knitr::opts_chunk$set(warning=FALSE)

Needed libraries

library(dplyr)
library(countrycode)
library(outliers)
library(caret)
library(cluster)
library(factoextra)
library(NbClust)

phase 1

Problem statement

Prediction of cyber security employees’ salaries based on 11 attributes

1.work_year

2.experience_level

3.employment_type

4.job_title

5.salary

6.salary_currency

7.salary_in_usd

8.employee_residence

9.remote_ratio

10.company_location

11.company_size

Problem description

We are living in the “information age” or rather the “data age”, meaning that everything around us revolves around data. The data has become one of the most valuable assets that a person or an organisation can have, since it has a significant value, losing it will lead to significant damages. Thus, most of the attacks nowadays are directed toward the data. To guard against such damages, organisations have realised the importance of protecting their digital assets, leading them to hire cybersecurity specialists. This made cybersecurity gain popularity among people so there’s a growing tendency to study cybersecurity. Consequently this resulted in the emergence of plentiful professionals with various experience levels and skills in this field. As a result, organisations may find it difficult to decide a salary for job candidates solely based on the CV. also, since the attacks improve rapidly, organisations need to hire more employees in the far future to defend against such attacks but it’s not an easy matter to predict the future payroll which may hinders some of the organisation’s plans. Another issue arises when the decision makers in the organisation aren’t fully aware of the trends on salary. Their lack of awareness gives a chance for the competitor organisations to attract their employees to them by offering a better salary that match current trends

Data mining task

Prediction of the cyber security employees’ salary categories (Very Low, Low, , High, Very High) using classification methods.

Goal

Given the problems we discussed and In order to better understand this field, we decided to analyse a dataset of 1247 cybersecurity employees, containing information such as salary, job title, and experience level. Analysing this dataset can provide insightful predictions regarding the salary range of a cybersecurity employee, which can help in

  • Making better decisions
  • Making recruitment and hiring process easier and more efficient
  • Predicting the future payroll
  • Increasing loyalty
  • Increasing the satisfaction rate
  • Achieving fairness

Source of data:

https://www.kaggle.com/datasets/deepcontractor/cyber-security-salaries

Reading and viewing dataset

dataset= read.csv(url("https://raw.githubusercontent.com/SarahAlhindi/DM_project/main/Data%20Set/salaries_cyber.csv"), header=TRUE)
View(dataset)

Original dataset

we will keep a copy of the original dataset before data preprocessing to use if needed at any time

originalDataset= dataset

General information about the dataset:

No. of attributes: 11
Type of attributes: Ordinal , Nominal, and Numeric
No. of objects: 1247
Class label: salary_in_usd

ncol(dataset)
nrow(dataset)
names(dataset)
str(dataset)

Attributes’ description table

Attribute Name Description Data Type Possible values
work_year The year in which salary was recorded Numerical 2020 to 2022
experience_level Expertise level of the employee Ordinal En “Entry level”
MI “Mid level”
SE “Senior level”
EX “Executive level”
employment_type The nature or category of employee’s engagement in the job Nominal PT “Part time”
FT “Full time”
CT “Contract
FL”Freelancer”
job_title The role worked in during the year Nominal

Different titles.

like Security Analyst, security researcher

salary The total gross salary amount paid Numerical 1740-50001566
salary_currency The currency of the salary paid to the employee Nominal

Different currencies according to ISO 4217 currency code.

like DE,CA

salary_in_usd The salary paid in United states dollar Numerical 2000 to 365596.40
employee_residence Employee’s primary country of residence Nominal

Different countries.

like US,AE

remote_ratio Percentage of online work by employee in the specified year Numerical 0 “No remote work”
50 “Partially remote”
100 “Fully remote”
company_location The country of the employer’s main office Nominal

Different countries.

like BR,BW

company_size How big/small is the company Ordinal S , M or L

phase 2

sample of 20 employees from the dataset:

using sample_n(table,size) function and using (set_seed())

set.seed(30)
sample=sample_n(dataset,20)
print(sample)

Show the missing value:

if it is FALSE it means no null value,if it is TRUE there is null value. In our dataset there is no null values.

is.na(dataset)
sum(is.na(dataset))

Show the Min.,1st Qu.,Median,Mean ,3rd Qu.,Max. for each numeric column

summary(dataset$work_year)
summary(dataset$salary)
summary(dataset$salary_in_usd)
summary(dataset$remote_ratio)

Show the variane of each numeric column

var(dataset$work_year)
var(dataset$salary)
var(dataset$salary_in_usd)
var(dataset$remote_ratio)

Visualization of relationship between some pairs of attributes:

Here we used boxplot to see the distribution between salary_in_usd and experience_level We observed that salaries vary depending on the level of experience,they are positively correlated.

boxplot(salary_in_usd ~ experience_level, data = dataset , yaxt="n")
labels<- pretty(dataset$salary_in_usd)
labels<- sapply(labels, function(x) format(x, scientific = FALSE))
axis(side = 2, at=pretty(dataset$salary_in_usd), labels = labels )
options(scipen = 999)

Here we used boxplot to see the distribution between salary_in_usd and work_year We observed that 2021 salaries were close to each other but in 2022 the gap between them getting bigger.

boxplot(salary_in_usd ~ work_year, data = dataset , yaxt="n")
labels<- pretty(dataset$salary_in_usd)
labels<- sapply(labels, function(x) format(x, scientific = FALSE))
axis(side = 2, at=pretty(dataset$salary_in_usd), labels = labels )
options(scipen = 999)

Here we used boxplot to see the distribution between salary_in_usd and employment_type We observed that Full Time (FT) offers more salary than the other categories.

boxplot(salary_in_usd ~ employment_type, data = dataset , yaxt="n")
labels<- pretty(dataset$salary_in_usd)
labels<- sapply(labels, function(x) format(x, scientific = FALSE))
axis(side = 2, at=pretty(dataset$salary_in_usd), labels = labels )
options(scipen = 999)

Here we used boxplot to see the distribution between salary_in_usd and company_size We observed that the larger the company is the higher the salary was.

boxplot(salary_in_usd ~ company_size, data = dataset , yaxt="n")
labels<- pretty(dataset$salary_in_usd)
labels<- sapply(labels, function(x) format(x, scientific = FALSE))
axis(side = 2, at=pretty(dataset$salary_in_usd), labels = labels )
options(scipen = 999) 

Data Reduction

Dimensionality Reduction

The “salary” column gives the same information as “salary_in_usd” it’s just a matter of currency exchange, and we will eventually transform all the values in “salary” column to one common currency so we can properly deal with them. To further confirm that the two column are redundant, we will use the latest exchange rate for USD to the desired currency.

we will start by creating a temporary column named “converted_salary” to save the salary that we will get by using the exchange rate to convert the salary_in_usd to the salary with different currencies to compare with “salary” column

convertedDataset=dataset


convertedDataset$exchange_rate = factor(convertedDataset$salary_currency, levels=c("USD","BRL","GBP","EUR","INR","CAD","CHF","DKK","SGD","AUD","SEK","MXN","ILS","PLN","NOK","IDR","NZD","HUF","ZAR","TWD","RUB"), labels=c(1/1,1/0.20,1/1.22,1/1.06,1/0.012,1/0.74,1/1.10,1/0.14,1/0.73,1/0.64,1/0.090,1/0.057,1/0.26,1/0.23,1/0.093,1/0.000065,1/0.60,1/0.0027,1/0.053,1/0.031,1/0.010))
convertedDataset$exchange_rate = as.numeric(as.character(convertedDataset$exchange_rate))
convertedDataset$converted_salary = convertedDataset$salary_in_usd*convertedDataset$exchange_rate



set.seed(1)
salary_sample <- sample_n(convertedDataset[,c("salary","converted_salary")],10)

print(salary_sample)

as shown in the sample, the two columns are almost identical. This can be proved by correlation coefficient as well.

correlation <- cor(convertedDataset$salary , convertedDataset$converted_salary)
print(correlation)

The correlation is so high but it hasn’t reached 100% possibly due to rounding in the calculations and slight differences in the exchange rate over time.

To make the mining process more effiecent and has an improved quality, we decided to remove the “salary” column.

dataset<-dataset[,-c(5)]

Find the outliers and remove them:

We will show outliers with boxPlots and then remove them, to minimize noise and to get better analytical results when applying data mining techniques.

now we show (salary_in_usd) attributes’ outliers. we can see that there are many outliers with exceptionally high values, thus we will remove them.

boxplot(dataset$salary_in_usd)



OutSalary = outlier(dataset$salary_in_usd, logical =TRUE)
Find_outlier = which(OutSalary ==TRUE, arr.ind = TRUE)
dataset= dataset[-Find_outlier,]

now we show (remote_ratio) attributes’ outliers. we can see there aren’t outliers in remote_ratio, thus we don’t need the last step i.e: removing outliers’ rows.

boxplot(dataset$remote_ratio)

now we show (work_year) attributes’ outliers. we can see there aren’t outliers in work_year, thus we don’t need the last step i.e: removing outliers’ rows.

boxplot(dataset$work_year)

Concept hierarchy generation for nominal data

the columns “company_location” and “employee_residence” have the name of countries for the company and employee respectively. And these attributes can be generalized to higher-level concept that is region to help understand and analyze the dataset better and improve algorithm performance.

We will use the 7 regions as defined in the World Bank Development Indicators. These regions are:

  1. East Asia and Pacific: This region includes countries like China, Australia, Indonesia, Thailand, etc.

  2. Europe and Central Asia: This region includes countries like Germany, UK, Russia, Turkey, etc.

  3. Latin America & Caribbean: This region includes countries like Brazil, Mexico, Argentina, Cuba, etc.

  4. Middle East and North Africa: This region includes countries like Saudi Arabia, Egypt, Iran, Iraq, etc.

  5. North America: This is predominantly United States and Canada.

  6. South Asia: This region includes countries like India, Pakistan, Bangladesh, Sri Lanka, etc.

  7. Sub-Saharan Africa: This region includes countries like Nigeria, South Africa, Ethiopia, Kenya, etc.

Note: UM(The United States Minor Outlying Islands) and AQ(Antarctica) don’t belong to any of these regions, thus, they will be used as they are.



um=which(dataset$company_location=="UM")
aq=which(dataset$company_location=="AQ")


dataset$company_location <- countrycode(dataset$company_location, "iso2c", "region")
dataset$employee_residence <- countrycode(dataset$employee_residence, "iso2c", "region")

dataset[um,"company_location"]="UM"
dataset[aq,"company_location"]="AQ"

Concept hierarchy generation can be done for “job_title” as well to improve interpretation and scalability. Also, most job titles are essentially the same job but with different names, so we can combine them into a higher-level jobs titles such as Architect, Analyst and Engineer etc.

## Create the categories based on job rank 
dataset$job_title <- ifelse(grepl("Analyst", dataset$job_title), "Analyst",
                                ifelse(grepl("Architect", dataset$job_title), "Architect",
                                       ifelse(grepl("Engineer", dataset$job_title), "Engineer",
                                              ifelse(grepl("Manager|Officer|Director|Leader", dataset$job_title), "Leadership",
                                                     ifelse(grepl("Consultant|Specialist", dataset$job_title), "Consultant/Specialist",
                                                            ifelse(grepl("Cyber", dataset$job_title), "Cyber Security",
                                                                   "Others"))))))

Encoding categorical data

To deal with columns with character type we are going to encode them, because most machine learning algorithms are designed to work with factors data rather than character data and it improves performance and Interpretability of data as well.

dataset$job_title  <- factor(dataset$job_title)

dataset$experience_level = factor(dataset$experience_level, levels=c("EN", "MI", "SE", "EX"), labels=c(1,2,3,4))

dataset$employment_type  <- factor(dataset$employment_type)

dataset$employee_residence  <- factor(dataset$employee_residence)

dataset$company_location  <- factor(dataset$company_location)

dataset$salary_currency  <- factor(dataset$salary_currency)

dataset$job_title  <- factor(dataset$job_title)


dataset$company_size = factor(dataset$company_size, levels=c("S","M","L"), labels=c(1,2,3))


dataset$job_title  <- factor(dataset$job_title)

Discretization of salaray_in_usd attribute

by calculating breaks based on quartiles

breaks <- quantile(dataset$salary_in_usd, 
                   probs = c(0, .25, .5, .75, .95, 1), 
                   na.rm = TRUE)


dataset$salary_in_usd <- cut(dataset$salary_in_usd, 
                                       breaks = breaks, 
                                       include.lowest = TRUE, 
                                       labels=c("Very Low", "Low", "Medium", "High", "Very High"))

Normalization:

to change the scale of numeric attributes (remote_ratio and work_year) to a scale of [-1,1] to give them equal weight

dataset [, c("work_year" , "remote_ratio")] = scale(dataset [, c("work_year" , "remote_ratio")])

Feature Selection

we will implement feature selection to remove redundant or irrelevant attributes from the data set to get the smallest subset that can help us get the most accurate predictions for our target class(salary_in_usd) and decrease the time that it takes the classifier to process the data.

we will use RFE(Recursive feature elimination) which is a wrapper method for the feature selection. Since the RFE function have multiple control options we need to specify the options that we want. We will choose “Random Forest” because it has high accuracy, can handle categorical data.

control <- rfeControl(functions = rfFuncs, 
                      method = "repeatedcv",
                      repeats = 5, 
                      number = 10)

First we save the features to be used in the feature selection(every attributes except the class label “salary_in_usd”) in variable x, and the class label in variable y. Then split the data to 80% training and 20% test.

x <- dataset %>%
  select(-salary_in_usd) %>%
  as.data.frame()

# Target variable
y <- dataset$salary_in_usd

# Training: 80%; Test: 20%
set.seed(2021)
inTrain <- createDataPartition(y, p = .80, list = FALSE)[,1]

x_train <- x[ inTrain, ]
x_test  <- x[-inTrain, ]

y_train <- y[ inTrain]
y_test  <- y[-inTrain]

after splitting the data, now we can perform the selection using rfe

set.seed(1)
result_rfe1 <- rfe(x = x_train, 
                   y = y_train, 
                   sizes = c(1:9),
                   rfeControl = control)

result_rfe1

predictors(result_rfe1)

The results show that all the remaining attributes, except for “employment_type”, are selected. This is logical, as 98% of the rows have the value “FT”, as shown in the table below. Due to the low variance, we decided to remove this attribute.

table(dataset$employment_type)
dataset<-dataset[,-which(names(dataset)=="employment_type")]

phase 3

dataset2= read.csv(url("https://raw.githubusercontent.com/SarahAlhindi/DM_project/main/Data%20Set/preprocessedDataset.csv"), header=TRUE)


char_vars <- sapply(dataset2, is.character)
dataset2[char_vars] <- lapply(dataset2[char_vars], as.factor)

balancing data

To resolve the problem of class imbalance in the dataset, we will use SMOTE() method that oversample the minority class by creating synthetic samples using the existing minority class samples

data_balanced <- SMOTE(salary_in_usd ~ ., dataset2, perc.over = 300, perc.under=500, k = 10)

Classification

The goal of all preceding steps is to properly prepare the dataset for the classification phase, which constitutes one of our primary mining objectives. In this section, we will employ various attribute selection methods such as the Gini index, Gain ratio, and information gain to construct a decision tree model. We will thoroughly evaluate its performance, and if it proves effective, it can subsequently be utilized to classify new instances with unknown class labels.

since our dataset is small, we decided to use K-fold Cross-validation. for each attribute selection method we will try different K size (10,5, and 3)

the following function will be used to copute average sensitivity and Specificity



macro = function(matrix){
  
  sumSen=0
  
  for (i in 1:5) {
   sumSen = sumSen + matrix$byClass[i,1] 
  }
  
  
  avgSen = sumSen/5
  
  sumSpec=0
  
  for (i in 1:5) {
   sumSpec = sumSpec + matrix$byClass[i,2] 
  }
  avgSpec = sumSpec/5
  
  
  avgs = data.frame(Sensitivity=avgSen , Specificity=avgSpec, Accuracy= unname( matrix$overall[1]) )
  print(avgs)
  
  
}

Gini index

Gini index measures the impurity of the dataset. The partitioning that yields the most substantial reduction in impurity is selected as the splitting attribute. To apply the Gini index, we will employ the “rpart” method, which utilizes the Gini index as the criteria for splitting.

10 Folds
set.seed(10)
ctrl <- trainControl(method = "cv", number = 10, returnResamp="all", savePredictions="final")

giniIndex10 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "rpart",trControl = ctrl)

prp(giniIndex10$finalModel, box.palette = "Reds", tweak = 1.2, varlen = 20)

NA
NA
NA
NA

caret::confusionMatrix(giniIndex10$pred$obs,giniIndex10$pred$pred)
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High         0  29    122        18        5
  Low          0 133     67         1       69
  Medium       0  71    134         9       23
  Very_High    0   5     98       146        3
  Very_Low     0  93     52         6      113

Overall Statistics
                                          
               Accuracy : 0.4394          
                 95% CI : (0.4111, 0.4681)
    No Information Rate : 0.3952          
    P-Value [Acc > NIR] : 0.001007        
                                          
                  Kappa : 0.2891          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity                   NA     0.4018        0.2833           0.8111
Specificity               0.8546     0.8418        0.8577           0.8958
Pos Pred Value                NA     0.4926        0.5654           0.5794
Neg Pred Value                NA     0.7864        0.6469           0.9640
Prevalence                0.0000     0.2765        0.3952           0.1504
Detection Rate            0.0000     0.1111        0.1119           0.1220
Detection Prevalence      0.1454     0.2256        0.1980           0.2105
Balanced Accuracy             NA     0.6218        0.5705           0.8534
                     Class: Very_Low
Sensitivity                   0.5305
Specificity                   0.8465
Pos Pred Value                0.4280
Neg Pred Value                0.8928
Prevalence                    0.1779
Detection Rate                0.0944
Detection Prevalence          0.2206
Balanced Accuracy             0.6885
5 Folds
set.seed(10)
ctrl <- trainControl(method = "cv", number = 5, returnResamp="all", savePredictions="final")

giniIndex5 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "rpart",trControl = ctrl)

prp(giniIndex5$finalModel, box.palette = "Reds", tweak = 1.2, varlen = 20)

NA
NA

caret::confusionMatrix(giniIndex5$pred$obs,giniIndex5$pred$pred)
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High         0  23     99        41       11
  Low          0 118     51        17       84
  Medium       0  66    107        36       28
  Very_High    0   4     82       162        4
  Very_Low     0  85     39        19      121

Overall Statistics
                                         
               Accuracy : 0.4244         
                 95% CI : (0.3962, 0.453)
    No Information Rate : 0.3158         
    P-Value [Acc > NIR] : 1.978e-15      
                                         
                  Kappa : 0.2692         
                                         
 Mcnemar's Test P-Value : < 2.2e-16      

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity                   NA    0.39865       0.28307           0.5891
Specificity               0.8546    0.83130       0.84127           0.9024
Pos Pred Value                NA    0.43704       0.45148           0.6429
Neg Pred Value                NA    0.80798       0.71771           0.8804
Prevalence                0.0000    0.24728       0.31579           0.2297
Detection Rate            0.0000    0.09858       0.08939           0.1353
Detection Prevalence      0.1454    0.22556       0.19799           0.2105
Balanced Accuracy             NA    0.61497       0.56217           0.7457
                     Class: Very_Low
Sensitivity                   0.4879
Specificity                   0.8493
Pos Pred Value                0.4583
Neg Pred Value                0.8639
Prevalence                    0.2072
Detection Rate                0.1011
Detection Prevalence          0.2206
Balanced Accuracy             0.6686
3 Folds
set.seed(10)
ctrl <- trainControl(method = "cv", number = 3, returnResamp="all", savePredictions="final")

giniIndex3 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "rpart",trControl = ctrl)

prp(giniIndex3$finalModel, box.palette = "Reds", tweak = 1.2, varlen = 20)

NA
NA

caret::confusionMatrix(giniIndex3$pred$obs,giniIndex3$pred$pred)
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High         0  30     76        62        6
  Low          0 161     33        28       48
  Medium       0  86     80        60       11
  Very_High    0   6     59       176       11
  Very_Low     0 103     20        27      114

Overall Statistics
                                          
               Accuracy : 0.4436          
                 95% CI : (0.4152, 0.4723)
    No Information Rate : 0.3225          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.292           
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity                   NA     0.4171       0.29851           0.4986
Specificity               0.8546     0.8656       0.83100           0.9100
Pos Pred Value                NA     0.5963       0.33755           0.6984
Neg Pred Value                NA     0.7573       0.80417           0.8127
Prevalence                0.0000     0.3225       0.22389           0.2949
Detection Rate            0.0000     0.1345       0.06683           0.1470
Detection Prevalence      0.1454     0.2256       0.19799           0.2105
Balanced Accuracy             NA     0.6413       0.56475           0.7043
                     Class: Very_Low
Sensitivity                  0.60000
Specificity                  0.85104
Pos Pred Value               0.43182
Neg Pred Value               0.91854
Prevalence                   0.15873
Detection Rate               0.09524
Detection Prevalence         0.22055
Balanced Accuracy            0.72552

Gain ratio

The gain ratio, a normalized measure of information gain, is calculated by dividing information gain by the split information. The attribute that yields the highest gain ratio is chosen as the splitting attribute. The C4.5 algorithm employs the gain ratio.

The J48 is the Java-based open-source implementation of the C4.5 algorithm, and it is included in the Weka package. This implementation allows users to conveniently apply the C4.5 decision tree.

10 Folds

set.seed(10)
ctrl <- trainControl(method = "cv", number = 10, returnResamp="all", savePredictions="final")
gainRatio10 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "J48",trControl = ctrl)
plot(gainRatio10$finalModel)

gainRatio10cm = caret::confusionMatrix(gainRatio10$pred$obs, gainRatio10$pred$pred)

gainRatio10cm
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High       100  18     32        21        3
  Low         28 148     40         3       51
  Medium      53  49    115        14        6
  Very_High   28   6     18       194        6
  Very_Low     2  39     11         5      207

Overall Statistics
                                          
               Accuracy : 0.6383          
                 95% CI : (0.6103, 0.6655)
    No Information Rate : 0.2281          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.5465          
                                          
 Mcnemar's Test P-Value : 0.167           

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity              0.47393     0.5692       0.53241           0.8186
Specificity              0.92495     0.8698       0.87564           0.9396
Pos Pred Value           0.57471     0.5481       0.48523           0.7698
Neg Pred Value           0.89150     0.8792       0.89479           0.9545
Prevalence               0.17627     0.2172       0.18045           0.1980
Detection Rate           0.08354     0.1236       0.09607           0.1621
Detection Prevalence     0.14536     0.2256       0.19799           0.2105
Balanced Accuracy        0.69944     0.7195       0.70402           0.8791
                     Class: Very_Low
Sensitivity                   0.7582
Specificity                   0.9383
Pos Pred Value                0.7841
Neg Pred Value                0.9293
Prevalence                    0.2281
Detection Rate                0.1729
Detection Prevalence          0.2206
Balanced Accuracy             0.8483

5 Folds

set.seed(10)
ctrl <- trainControl(method = "cv", number = 5, returnResamp="all", savePredictions="final")
gainRatio5 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "J48",trControl = ctrl)
plot(gainRatio5$finalModel)


gainRatio5cm=caret::confusionMatrix(gainRatio5$pred$obs, gainRatio5$pred$pred)

gainRatio5cm
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High       102  21     34        16        1
  Low         31 148     38         1       52
  Medium      56  46    103        15       17
  Very_High   30   7     18       194        3
  Very_Low     3  42     10         9      200

Overall Statistics
                                          
               Accuracy : 0.6241          
                 95% CI : (0.5959, 0.6516)
    No Information Rate : 0.2281          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5289          
                                          
 Mcnemar's Test P-Value : 0.007667        

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity              0.45946     0.5606       0.50739           0.8255
Specificity              0.92615     0.8692       0.86519           0.9397
Pos Pred Value           0.58621     0.5481       0.43460           0.7698
Neg Pred Value           0.88270     0.8749       0.89583           0.9566
Prevalence               0.18546     0.2206       0.16959           0.1963
Detection Rate           0.08521     0.1236       0.08605           0.1621
Detection Prevalence     0.14536     0.2256       0.19799           0.2105
Balanced Accuracy        0.69281     0.7149       0.68629           0.8826
                     Class: Very_Low
Sensitivity                   0.7326
Specificity                   0.9307
Pos Pred Value                0.7576
Neg Pred Value                0.9218
Prevalence                    0.2281
Detection Rate                0.1671
Detection Prevalence          0.2206
Balanced Accuracy             0.8317

3 Folds

set.seed(10)
ctrl <- trainControl(method = "cv", number = 3, returnResamp="all", savePredictions="final")
gainRatio3 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "J48",trControl = ctrl)
plot(gainRatio3$finalModel)

gainRatio3cm=caret::confusionMatrix(gainRatio3$pred$obs, gainRatio3$pred$pred)

gainRatio3cm
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High        94  18     39        19        4
  Low         25 129     39         3       74
  Medium      47  47    110        19       14
  Very_High   14   4     27       200        7
  Very_Low     3  32      9        12      208

Overall Statistics
                                          
               Accuracy : 0.619           
                 95% CI : (0.5909, 0.6467)
    No Information Rate : 0.2565          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5216          
                                          
 Mcnemar's Test P-Value : 0.007322        

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity              0.51366     0.5609        0.4911           0.7905
Specificity              0.92110     0.8542        0.8695           0.9449
Pos Pred Value           0.54023     0.4778        0.4641           0.7937
Neg Pred Value           0.91300     0.8910        0.8812           0.9439
Prevalence               0.15288     0.1921        0.1871           0.2114
Detection Rate           0.07853     0.1078        0.0919           0.1671
Detection Prevalence     0.14536     0.2256        0.1980           0.2105
Balanced Accuracy        0.71738     0.7075        0.6803           0.8677
                     Class: Very_Low
Sensitivity                   0.6775
Specificity                   0.9371
Pos Pred Value                0.7879
Neg Pred Value                0.8939
Prevalence                    0.2565
Detection Rate                0.1738
Detection Prevalence          0.2206
Balanced Accuracy             0.8073

analasys of the gain ratio classification

all 3 trees seem to have the same structure that is

the attribute that was first selected at the node is the experience level, it has divided the tree into : right subtree : SE(Senior level) EX(Executive level) left subtree : EN(Entry-level) MI(Mid level)

Each of these subtrees further refines the classification based on the attribute “employee residence.” However, there are different criteria for splitting in the right and left subtrees:

In the Right Subtree:

The split is based on whether the tuple has the value “Latin America & Caribbean.” In the Left Subtree:

If the experience level is 1, the tree further partitions based on whether the tuple has the value “North America.” If the experience level is 2, the split is based on “employee residence” being “Latin America & Caribbean.”

rbind("10 Folds"=macro(gainRatio10cm), "5 Folds"=macro(gainRatio5cm), "3 Folds"=macro(gainRatio3cm)  )

Based on the evaluation metrics of Sensitivity, Specificity, and Accuracy, it is evident that the gain ratio model, built using a 10-fold cross-validation approach, exhibits superior performance compared to the other two models. However, it’s worth noting that the difference in performance between the models is relatively small. Notably, as the number of folds decreases, a corresponding decline in the model’s performance becomes apparent.

Information gain

10 Folds

set.seed(10)
ctrl <- trainControl(method = "cv", number = 10, returnResamp="all", savePredictions="final")


infoGain10 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "C5.0",trControl = ctrl)
Warning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 4 for this object. Predictions generated using 4 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 4 for this object. Predictions generated using 4 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 4 for this object. Predictions generated using 4 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 3 for this object. Predictions generated using 3 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trials
c5model <- C5.0(salary_in_usd ~ .,
                       data = data_balanced,
                       trials = infoGain10$bestTune$trials, 
                       rules = FALSE,
                       control = C5.0Control(winnow = infoGain10$bestTune$winnow))
plot(c5model)

caret::confusionMatrix(infoGain10$pred$obs, infoGain10$pred$pred)
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High        90  19     40        20        5
  Low         24 127     42        10       67
  Medium      51  58     96        19       13
  Very_High   17   3     15       208        9
  Very_Low     3  43      4        10      204

Overall Statistics
                                          
               Accuracy : 0.6057          
                 95% CI : (0.5773, 0.6335)
    No Information Rate : 0.249           
    P-Value [Acc > NIR] : < 2e-16         
                                          
                  Kappa : 0.5046          
                                          
 Mcnemar's Test P-Value : 0.03427         

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity              0.48649     0.5080        0.4873           0.7790
Specificity              0.91700     0.8490        0.8590           0.9527
Pos Pred Value           0.51724     0.4704        0.4051           0.8254
Neg Pred Value           0.90714     0.8673        0.8948           0.9376
Prevalence               0.15455     0.2089        0.1646           0.2231
Detection Rate           0.07519     0.1061        0.0802           0.1738
Detection Prevalence     0.14536     0.2256        0.1980           0.2105
Balanced Accuracy        0.70174     0.6785        0.6732           0.8659
                     Class: Very_Low
Sensitivity                   0.6846
Specificity                   0.9333
Pos Pred Value                0.7727
Neg Pred Value                0.8992
Prevalence                    0.2490
Detection Rate                0.1704
Detection Prevalence          0.2206
Balanced Accuracy             0.8089

5 Folds

set.seed(10)
ctrl <- trainControl(method = "cv", number = 5, returnResamp="all", savePredictions="final")


infoGain5 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "C5.0",trControl = ctrl)
Warning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 8 for this object. Predictions generated using 8 trials
c5model <- C5.0(salary_in_usd ~ .,
                       data = data_balanced,
                       trials = infoGain5$bestTune$trials, 
                       rules = FALSE,
                       control = C5.0Control(winnow = infoGain5$bestTune$winnow))
plot(c5model)

caret::confusionMatrix(infoGain5$pred$obs, infoGain5$pred$pred)
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High        85  20     47        16        6
  Low         25 129     38        10       68
  Medium      37  70     99        14       17
  Very_High   16   4     13       210        9
  Very_Low     2  37      9        13      203

Overall Statistics
                                          
               Accuracy : 0.6065          
                 95% CI : (0.5782, 0.6343)
    No Information Rate : 0.2531          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5049          
                                          
 Mcnemar's Test P-Value : 0.001691        

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity              0.51515     0.4962       0.48058           0.7985
Specificity              0.91376     0.8495       0.86075           0.9550
Pos Pred Value           0.48851     0.4778       0.41772           0.8333
Neg Pred Value           0.92180     0.8587       0.88854           0.9439
Prevalence               0.13784     0.2172       0.17210           0.2197
Detection Rate           0.07101     0.1078       0.08271           0.1754
Detection Prevalence     0.14536     0.2256       0.19799           0.2105
Balanced Accuracy        0.71446     0.6728       0.67066           0.8768
                     Class: Very_Low
Sensitivity                   0.6700
Specificity                   0.9318
Pos Pred Value                0.7689
Neg Pred Value                0.8928
Prevalence                    0.2531
Detection Rate                0.1696
Detection Prevalence          0.2206
Balanced Accuracy             0.8009

3 Folds

set.seed(10)
ctrl <- trainControl(method = "cv", number = 3, returnResamp="all", savePredictions="final")


infoGain3 <- train(salary_in_usd ~ ., data = balanced_dataset, method = "C5.0",trControl = ctrl)
Warning: 'trials' should be <= 4 for this object. Predictions generated using 4 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 5 for this object. Predictions generated using 5 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 7 for this object. Predictions generated using 7 trialsWarning: 'trials' should be <= 9 for this object. Predictions generated using 9 trialsWarning: 'trials' should be <= 6 for this object. Predictions generated using 6 trials
c5model <- C5.0(salary_in_usd ~ .,
                       data = data_balanced,
                       trials = infoGain3$bestTune$trials, 
                       rules = FALSE,
                       control = C5.0Control(winnow = infoGain3$bestTune$winnow))
plot(c5model)

caret::confusionMatrix(infoGain3$pred$obs, infoGain3$pred$pred)
Confusion Matrix and Statistics

           Reference
Prediction  High Low Medium Very_High Very_Low
  High        85  25     40        19        5
  Low         19 137     37         5       72
  Medium      55  64     93        11       14
  Very_High   16   8     22       197        9
  Very_Low     4  43     11         9      197

Overall Statistics
                                          
               Accuracy : 0.5923          
                 95% CI : (0.5639, 0.6203)
    No Information Rate : 0.2481          
    P-Value [Acc > NIR] : < 2e-16         
                                          
                  Kappa : 0.4874          
                                          
 Mcnemar's Test P-Value : 0.01149         

Statistics by Class:

                     Class: High Class: Low Class: Medium Class: Very_High
Sensitivity              0.47486     0.4946       0.45813           0.8174
Specificity              0.91257     0.8554       0.85513           0.9425
Pos Pred Value           0.48851     0.5074       0.39241           0.7817
Neg Pred Value           0.90811     0.8490       0.88542           0.9534
Prevalence               0.14954     0.2314       0.16959           0.2013
Detection Rate           0.07101     0.1145       0.07769           0.1646
Detection Prevalence     0.14536     0.2256       0.19799           0.2105
Balanced Accuracy        0.69372     0.6750       0.65663           0.8799
                     Class: Very_Low
Sensitivity                   0.6633
Specificity                   0.9256
Pos Pred Value                0.7462
Neg Pred Value                0.8928
Prevalence                    0.2481
Detection Rate                0.1646
Detection Prevalence          0.2206
Balanced Accuracy             0.7944

Silhouette method

#a)fviz_nbclust() with silhouette method using library(factoextra) 
fviz_nbclust(dataset, kmeans, method = "silhouette")+
  labs(subtitle = "Silhouette method")
Error in fviz_nbclust(dataset, kmeans, method = "silhouette") : 
  could not find function "fviz_nbclust"
LS0tDQp0aXRsZTogIkN5YmVyc2VjdXJpdHkgc2FsYXJpZXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCg0KYGBge3J9DQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nPUZBTFNFKQ0KDQpgYGANCg0KDQoNCiMjIyBOZWVkZWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoY291bnRyeWNvZGUpDQpsaWJyYXJ5KG91dGxpZXJzKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoTmJDbHVzdCkNCmBgYA0KDQojIHBoYXNlIDENCg0KIyMjIFByb2JsZW0gc3RhdGVtZW50DQoNClByZWRpY3Rpb24gb2YgY3liZXIgc2VjdXJpdHkgZW1wbG95ZWVzJyBzYWxhcmllcyBiYXNlZCBvbiAxMSBhdHRyaWJ1dGVzDQoNCjEud29ya195ZWFyDQoNCjIuZXhwZXJpZW5jZV9sZXZlbA0KDQozLmVtcGxveW1lbnRfdHlwZQ0KDQo0LmpvYl90aXRsZQ0KDQo1LnNhbGFyeQ0KDQo2LnNhbGFyeV9jdXJyZW5jeQ0KDQo3LnNhbGFyeV9pbl91c2QNCg0KOC5lbXBsb3llZV9yZXNpZGVuY2UNCg0KOS5yZW1vdGVfcmF0aW8NCg0KMTAuY29tcGFueV9sb2NhdGlvbg0KDQoxMS5jb21wYW55X3NpemUNCg0KIyMjIFByb2JsZW0gZGVzY3JpcHRpb24NCg0KV2UgYXJlIGxpdmluZyBpbiB0aGUgImluZm9ybWF0aW9uIGFnZSIgb3IgcmF0aGVyIHRoZSAiZGF0YSBhZ2UiLCBtZWFuaW5nIHRoYXQgZXZlcnl0aGluZyBhcm91bmQgdXMgcmV2b2x2ZXMgYXJvdW5kIGRhdGEuIFRoZSBkYXRhIGhhcyBiZWNvbWUgb25lIG9mIHRoZSBtb3N0IHZhbHVhYmxlIGFzc2V0cyB0aGF0IGEgcGVyc29uIG9yIGFuIG9yZ2FuaXNhdGlvbiBjYW4gaGF2ZSwgc2luY2UgaXQgaGFzIGEgc2lnbmlmaWNhbnQgdmFsdWUsIGxvc2luZyBpdCB3aWxsIGxlYWQgdG8gc2lnbmlmaWNhbnQgZGFtYWdlcy4gVGh1cywgbW9zdCBvZiB0aGUgYXR0YWNrcyBub3dhZGF5cyBhcmUgZGlyZWN0ZWQgdG93YXJkIHRoZSBkYXRhLiBUbyBndWFyZCBhZ2FpbnN0IHN1Y2ggZGFtYWdlcywgb3JnYW5pc2F0aW9ucyBoYXZlIHJlYWxpc2VkIHRoZSBpbXBvcnRhbmNlIG9mIHByb3RlY3RpbmcgdGhlaXIgZGlnaXRhbCBhc3NldHMsIGxlYWRpbmcgdGhlbSB0byBoaXJlIGN5YmVyc2VjdXJpdHkgc3BlY2lhbGlzdHMuIFRoaXMgbWFkZSBjeWJlcnNlY3VyaXR5IGdhaW4gcG9wdWxhcml0eSBhbW9uZyBwZW9wbGUgc28gdGhlcmUncyBhIGdyb3dpbmcgdGVuZGVuY3kgdG8gc3R1ZHkgY3liZXJzZWN1cml0eS4gQ29uc2VxdWVudGx5IHRoaXMgcmVzdWx0ZWQgaW4gdGhlIGVtZXJnZW5jZSBvZiBwbGVudGlmdWwgcHJvZmVzc2lvbmFscyB3aXRoIHZhcmlvdXMgZXhwZXJpZW5jZSBsZXZlbHMgYW5kIHNraWxscyBpbiB0aGlzIGZpZWxkLiBBcyBhIHJlc3VsdCwgb3JnYW5pc2F0aW9ucyBtYXkgZmluZCBpdCBkaWZmaWN1bHQgdG8gZGVjaWRlIGEgc2FsYXJ5IGZvciBqb2IgY2FuZGlkYXRlcyBzb2xlbHkgYmFzZWQgb24gdGhlIENWLiBhbHNvLCBzaW5jZSB0aGUgYXR0YWNrcyBpbXByb3ZlIHJhcGlkbHksIG9yZ2FuaXNhdGlvbnMgbmVlZCB0byBoaXJlIG1vcmUgZW1wbG95ZWVzIGluIHRoZSBmYXIgZnV0dXJlIHRvIGRlZmVuZCBhZ2FpbnN0IHN1Y2ggYXR0YWNrcyBidXQgaXQncyBub3QgYW4gZWFzeSBtYXR0ZXIgdG8gcHJlZGljdCB0aGUgZnV0dXJlIHBheXJvbGwgd2hpY2ggbWF5IGhpbmRlcnMgc29tZSBvZiB0aGUgb3JnYW5pc2F0aW9uJ3MgcGxhbnMuIEFub3RoZXIgaXNzdWUgYXJpc2VzIHdoZW4gdGhlIGRlY2lzaW9uIG1ha2VycyBpbiB0aGUgb3JnYW5pc2F0aW9uIGFyZW4ndCBmdWxseSBhd2FyZSBvZiB0aGUgdHJlbmRzIG9uIHNhbGFyeS4gVGhlaXIgbGFjayBvZiBhd2FyZW5lc3MgZ2l2ZXMgYSBjaGFuY2UgZm9yIHRoZSBjb21wZXRpdG9yIG9yZ2FuaXNhdGlvbnMgdG8gYXR0cmFjdCB0aGVpciBlbXBsb3llZXMgdG8gdGhlbSBieSBvZmZlcmluZyBhIGJldHRlciBzYWxhcnkgdGhhdCBtYXRjaCBjdXJyZW50IHRyZW5kcw0KDQojIyMgRGF0YSBtaW5pbmcgdGFzaw0KDQpQcmVkaWN0aW9uIG9mIHRoZSBjeWJlciBzZWN1cml0eSBlbXBsb3llZXMnIHNhbGFyeSBjYXRlZ29yaWVzIChWZXJ5IExvdywgTG93LCAsIEhpZ2gsIFZlcnkgSGlnaCkgdXNpbmcgY2xhc3NpZmljYXRpb24gbWV0aG9kcy4NCg0KIyMjIEdvYWwNCg0KR2l2ZW4gdGhlIHByb2JsZW1zIHdlIGRpc2N1c3NlZCBhbmQgSW4gb3JkZXIgdG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhpcyBmaWVsZCwgd2UgZGVjaWRlZCB0byBhbmFseXNlIGEgZGF0YXNldCBvZiAxMjQ3IGN5YmVyc2VjdXJpdHkgZW1wbG95ZWVzLCBjb250YWluaW5nIGluZm9ybWF0aW9uIHN1Y2ggYXMgc2FsYXJ5LCBqb2IgdGl0bGUsIGFuZCBleHBlcmllbmNlIGxldmVsLiBBbmFseXNpbmcgdGhpcyBkYXRhc2V0IGNhbiBwcm92aWRlIGluc2lnaHRmdWwgcHJlZGljdGlvbnMgcmVnYXJkaW5nIHRoZSBzYWxhcnkgcmFuZ2Ugb2YgYSBjeWJlcnNlY3VyaXR5IGVtcGxveWVlLCB3aGljaCBjYW4gaGVscCBpbg0KDQotICAgTWFraW5nIGJldHRlciBkZWNpc2lvbnMNCi0gICBNYWtpbmcgcmVjcnVpdG1lbnQgYW5kIGhpcmluZyBwcm9jZXNzIGVhc2llciBhbmQgbW9yZSBlZmZpY2llbnQNCi0gICBQcmVkaWN0aW5nIHRoZSBmdXR1cmUgcGF5cm9sbA0KLSAgIEluY3JlYXNpbmcgbG95YWx0eQ0KLSAgIEluY3JlYXNpbmcgdGhlIHNhdGlzZmFjdGlvbiByYXRlDQotICAgQWNoaWV2aW5nIGZhaXJuZXNzDQoNCiMjIFNvdXJjZSBvZiBkYXRhOg0KDQo8aHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9kZWVwY29udHJhY3Rvci9jeWJlci1zZWN1cml0eS1zYWxhcmllcz4NCg0KIyMjIFJlYWRpbmcgYW5kIHZpZXdpbmcgZGF0YXNldA0KDQpgYGB7cn0NCmRhdGFzZXQ9IHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1NhcmFoQWxoaW5kaS9ETV9wcm9qZWN0L21haW4vRGF0YSUyMFNldC9zYWxhcmllc19jeWJlci5jc3YiKSwgaGVhZGVyPVRSVUUpDQpWaWV3KGRhdGFzZXQpDQoNCmBgYA0KDQojIyMgT3JpZ2luYWwgZGF0YXNldA0KDQp3ZSB3aWxsIGtlZXAgYSBjb3B5IG9mIHRoZSBvcmlnaW5hbCBkYXRhc2V0IGJlZm9yZSBkYXRhIHByZXByb2Nlc3NpbmcgdG8gdXNlIGlmIG5lZWRlZCBhdCBhbnkgdGltZQ0KDQpgYGB7cn0NCm9yaWdpbmFsRGF0YXNldD0gZGF0YXNldA0KYGBgDQoNCiMjIEdlbmVyYWwgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGRhdGFzZXQ6DQoNCk5vLiBvZiBhdHRyaWJ1dGVzOiAxMVwNClR5cGUgb2YgYXR0cmlidXRlczogT3JkaW5hbCAsIE5vbWluYWwsIGFuZCBOdW1lcmljXA0KTm8uIG9mIG9iamVjdHM6IDEyNDdcDQpDbGFzcyBsYWJlbDogc2FsYXJ5X2luX3VzZA0KDQpgYGB7cn0NCm5jb2woZGF0YXNldCkNCm5yb3coZGF0YXNldCkNCm5hbWVzKGRhdGFzZXQpDQpzdHIoZGF0YXNldCkNCmBgYA0KDQojIyMgQXR0cmlidXRlcycgZGVzY3JpcHRpb24gdGFibGUNCg0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAqKkF0dHJpYnV0ZSBOYW1lKiogfCAqKkRlc2NyaXB0aW9uKiogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICoqRGF0YSBUeXBlKiogfCAqKlBvc3NpYmxlIHZhbHVlcyoqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKz09PT09PT09PT09PT09PT09PT09Kz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0rPT09PT09PT09PT09PT09Kz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Kw0KfCB3b3JrX3llYXIgICAgICAgICAgfCBUaGUgeWVhciBpbiB3aGljaCBzYWxhcnkgd2FzIHJlY29yZGVkICAgICAgICAgICAgICAgICAgICAgICB8IE51bWVyaWNhbCAgICAgfCAyMDIwIHRvIDIwMjIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBleHBlcmllbmNlX2xldmVsICAgfCBFeHBlcnRpc2UgbGV2ZWwgb2YgdGhlIGVtcGxveWVlICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IE9yZGluYWwgICAgICAgfCBFbiAiRW50cnkgbGV2ZWwiXCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBNSSAiTWlkIGxldmVsIlwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBTRSAiU2VuaW9yIGxldmVsIlwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBFWCAiRXhlY3V0aXZlIGxldmVsIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBlbXBsb3ltZW50X3R5cGUgICAgfCBUaGUgbmF0dXJlIG9yIGNhdGVnb3J5IG9mIGVtcGxveWVlJ3MgZW5nYWdlbWVudCBpbiB0aGUgam9iICB8IE5vbWluYWwgICAgICAgfCBQVCAiUGFydCB0aW1lIlwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBGVCAiRnVsbCB0aW1lIlwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBDVCAiQ29udHJhY3RcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBGTCJGcmVlbGFuY2VyIiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBqb2JfdGl0bGUgICAgICAgICAgfCBUaGUgcm9sZSB3b3JrZWQgaW4gZHVyaW5nIHRoZSB5ZWFyICAgICAgICAgICAgICAgICAgICAgICAgICB8IE5vbWluYWwgICAgICAgfCBEaWZmZXJlbnQgdGl0bGVzLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBsaWtlIFNlY3VyaXR5IEFuYWx5c3QsIHNlY3VyaXR5IHJlc2VhcmNoZXIgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBzYWxhcnkgICAgICAgICAgICAgfCBUaGUgdG90YWwgZ3Jvc3Mgc2FsYXJ5IGFtb3VudCBwYWlkICAgICAgICAgICAgICAgICAgICAgICAgICB8IE51bWVyaWNhbCAgICAgfCAxNzQwLTUwMDAxNTY2ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBzYWxhcnlfY3VycmVuY3kgICAgfCBUaGUgY3VycmVuY3kgb2YgdGhlIHNhbGFyeSBwYWlkIHRvIHRoZSBlbXBsb3llZSAgICAgICAgICAgICB8IE5vbWluYWwgICAgICAgfCBEaWZmZXJlbnQgY3VycmVuY2llcyBhY2NvcmRpbmcgdG8gSVNPIDQyMTcgY3VycmVuY3kgY29kZS4gfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBsaWtlIERFLENBICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBzYWxhcnlfaW5fdXNkICAgICAgfCBUaGUgc2FsYXJ5IHBhaWQgaW4gVW5pdGVkIHN0YXRlcyBkb2xsYXIgICAgICAgICAgICAgICAgICAgICB8IE51bWVyaWNhbCAgICAgfCAyMDAwIHRvIDM2NTU5Ni40MCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBlbXBsb3llZV9yZXNpZGVuY2UgfCBFbXBsb3llZSdzIHByaW1hcnkgY291bnRyeSBvZiByZXNpZGVuY2UgICAgICAgICAgICAgICAgICAgICB8IE5vbWluYWwgICAgICAgfCBEaWZmZXJlbnQgY291bnRyaWVzLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBsaWtlIFVTLEFFICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCByZW1vdGVfcmF0aW8gICAgICAgfCBQZXJjZW50YWdlIG9mIG9ubGluZSB3b3JrIGJ5IGVtcGxveWVlIGluIHRoZSBzcGVjaWZpZWQgeWVhciB8IE51bWVyaWNhbCAgICAgfCAwICJObyByZW1vdGUgd29yayJcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCA1MCAiUGFydGlhbGx5IHJlbW90ZSJcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCAxMDAgIkZ1bGx5IHJlbW90ZSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBjb21wYW55X2xvY2F0aW9uICAgfCBUaGUgY291bnRyeSBvZiB0aGUgZW1wbG95ZXIncyBtYWluIG9mZmljZSAgICAgICAgICAgICAgICAgICB8IE5vbWluYWwgICAgICAgfCBEaWZmZXJlbnQgY291bnRyaWVzLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgfCBsaWtlIEJSLEJXICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBjb21wYW55X3NpemUgICAgICAgfCBIb3cgYmlnL3NtYWxsIGlzIHRoZSBjb21wYW55ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IE9yZGluYWwgICAgICAgfCBTICwgTSBvciBMICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KDQojIHBoYXNlIDINCg0KIyMjIHNhbXBsZSBvZiAyMCBlbXBsb3llZXMgZnJvbSB0aGUgZGF0YXNldDoNCg0KdXNpbmcgc2FtcGxlX24odGFibGUsc2l6ZSkgZnVuY3Rpb24gYW5kIHVzaW5nIChzZXRfc2VlZCgpKQ0KDQpgYGB7cn0NCnNldC5zZWVkKDMwKQ0Kc2FtcGxlPXNhbXBsZV9uKGRhdGFzZXQsMjApDQpwcmludChzYW1wbGUpDQpgYGANCg0KIyMjIFNob3cgdGhlIG1pc3NpbmcgdmFsdWU6DQoNCmlmIGl0IGlzIEZBTFNFIGl0IG1lYW5zIG5vIG51bGwgdmFsdWUsaWYgaXQgaXMgVFJVRSB0aGVyZSBpcyBudWxsIHZhbHVlLiBJbiBvdXIgZGF0YXNldCB0aGVyZSBpcyBubyBudWxsIHZhbHVlcy4NCg0KYGBge3J9DQppcy5uYShkYXRhc2V0KQ0Kc3VtKGlzLm5hKGRhdGFzZXQpKQ0KYGBgDQoNCiMjIyBTaG93IHRoZSBNaW4uLDFzdCBRdS4sTWVkaWFuLE1lYW4gLDNyZCBRdS4sTWF4LiBmb3IgZWFjaCBudW1lcmljIGNvbHVtbg0KDQpgYGB7cn0NCnN1bW1hcnkoZGF0YXNldCR3b3JrX3llYXIpDQpzdW1tYXJ5KGRhdGFzZXQkc2FsYXJ5KQ0Kc3VtbWFyeShkYXRhc2V0JHNhbGFyeV9pbl91c2QpDQpzdW1tYXJ5KGRhdGFzZXQkcmVtb3RlX3JhdGlvKQ0KYGBgDQoNCiMjIyBTaG93IHRoZSB2YXJpYW5lIG9mIGVhY2ggbnVtZXJpYyBjb2x1bW4NCg0KYGBge3J9DQp2YXIoZGF0YXNldCR3b3JrX3llYXIpDQp2YXIoZGF0YXNldCRzYWxhcnkpDQp2YXIoZGF0YXNldCRzYWxhcnlfaW5fdXNkKQ0KdmFyKGRhdGFzZXQkcmVtb3RlX3JhdGlvKQ0KYGBgDQoNCiMjIyBWaXN1YWxpemF0aW9uIG9mIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNvbWUgcGFpcnMgb2YgYXR0cmlidXRlczoNCg0KSGVyZSB3ZSB1c2VkIGJveHBsb3QgdG8gc2VlIHRoZSBkaXN0cmlidXRpb24gYmV0d2VlbiBzYWxhcnlfaW5fdXNkIGFuZCBleHBlcmllbmNlX2xldmVsIFdlIG9ic2VydmVkIHRoYXQgc2FsYXJpZXMgdmFyeSBkZXBlbmRpbmcgb24gdGhlIGxldmVsIG9mIGV4cGVyaWVuY2UsdGhleSBhcmUgcG9zaXRpdmVseSBjb3JyZWxhdGVkLg0KDQpgYGB7cn0NCmJveHBsb3Qoc2FsYXJ5X2luX3VzZCB+IGV4cGVyaWVuY2VfbGV2ZWwsIGRhdGEgPSBkYXRhc2V0ICwgeWF4dD0ibiIpDQpsYWJlbHM8LSBwcmV0dHkoZGF0YXNldCRzYWxhcnlfaW5fdXNkKQ0KbGFiZWxzPC0gc2FwcGx5KGxhYmVscywgZnVuY3Rpb24oeCkgZm9ybWF0KHgsIHNjaWVudGlmaWMgPSBGQUxTRSkpDQpheGlzKHNpZGUgPSAyLCBhdD1wcmV0dHkoZGF0YXNldCRzYWxhcnlfaW5fdXNkKSwgbGFiZWxzID0gbGFiZWxzICkNCm9wdGlvbnMoc2NpcGVuID0gOTk5KQ0KYGBgDQoNCkhlcmUgd2UgdXNlZCBib3hwbG90IHRvIHNlZSB0aGUgZGlzdHJpYnV0aW9uIGJldHdlZW4gc2FsYXJ5X2luX3VzZCBhbmQgd29ya195ZWFyIFdlIG9ic2VydmVkIHRoYXQgMjAyMSBzYWxhcmllcyB3ZXJlIGNsb3NlIHRvIGVhY2ggb3RoZXIgYnV0IGluIDIwMjIgdGhlIGdhcCBiZXR3ZWVuIHRoZW0gZ2V0dGluZyBiaWdnZXIuDQoNCmBgYHtyfQ0KYm94cGxvdChzYWxhcnlfaW5fdXNkIH4gd29ya195ZWFyLCBkYXRhID0gZGF0YXNldCAsIHlheHQ9Im4iKQ0KbGFiZWxzPC0gcHJldHR5KGRhdGFzZXQkc2FsYXJ5X2luX3VzZCkNCmxhYmVsczwtIHNhcHBseShsYWJlbHMsIGZ1bmN0aW9uKHgpIGZvcm1hdCh4LCBzY2llbnRpZmljID0gRkFMU0UpKQ0KYXhpcyhzaWRlID0gMiwgYXQ9cHJldHR5KGRhdGFzZXQkc2FsYXJ5X2luX3VzZCksIGxhYmVscyA9IGxhYmVscyApDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmBgYA0KDQpIZXJlIHdlIHVzZWQgYm94cGxvdCB0byBzZWUgdGhlIGRpc3RyaWJ1dGlvbiBiZXR3ZWVuIHNhbGFyeV9pbl91c2QgYW5kIGVtcGxveW1lbnRfdHlwZSBXZSBvYnNlcnZlZCB0aGF0IEZ1bGwgVGltZSAoRlQpIG9mZmVycyBtb3JlIHNhbGFyeSB0aGFuIHRoZSBvdGhlciBjYXRlZ29yaWVzLg0KDQpgYGB7cn0NCmJveHBsb3Qoc2FsYXJ5X2luX3VzZCB+IGVtcGxveW1lbnRfdHlwZSwgZGF0YSA9IGRhdGFzZXQgLCB5YXh0PSJuIikNCmxhYmVsczwtIHByZXR0eShkYXRhc2V0JHNhbGFyeV9pbl91c2QpDQpsYWJlbHM8LSBzYXBwbHkobGFiZWxzLCBmdW5jdGlvbih4KSBmb3JtYXQoeCwgc2NpZW50aWZpYyA9IEZBTFNFKSkNCmF4aXMoc2lkZSA9IDIsIGF0PXByZXR0eShkYXRhc2V0JHNhbGFyeV9pbl91c2QpLCBsYWJlbHMgPSBsYWJlbHMgKQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpgYGANCg0KSGVyZSB3ZSB1c2VkIGJveHBsb3QgdG8gc2VlIHRoZSBkaXN0cmlidXRpb24gYmV0d2VlbiBzYWxhcnlfaW5fdXNkIGFuZCBjb21wYW55X3NpemUgV2Ugb2JzZXJ2ZWQgdGhhdCB0aGUgbGFyZ2VyIHRoZSBjb21wYW55IGlzIHRoZSBoaWdoZXIgdGhlIHNhbGFyeSB3YXMuDQoNCmBgYHtyfQ0KYm94cGxvdChzYWxhcnlfaW5fdXNkIH4gY29tcGFueV9zaXplLCBkYXRhID0gZGF0YXNldCAsIHlheHQ9Im4iKQ0KbGFiZWxzPC0gcHJldHR5KGRhdGFzZXQkc2FsYXJ5X2luX3VzZCkNCmxhYmVsczwtIHNhcHBseShsYWJlbHMsIGZ1bmN0aW9uKHgpIGZvcm1hdCh4LCBzY2llbnRpZmljID0gRkFMU0UpKQ0KYXhpcyhzaWRlID0gMiwgYXQ9cHJldHR5KGRhdGFzZXQkc2FsYXJ5X2luX3VzZCksIGxhYmVscyA9IGxhYmVscyApDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkgDQpgYGANCg0KIyMgRGF0YSBSZWR1Y3Rpb24NCg0KIyMjIERpbWVuc2lvbmFsaXR5IFJlZHVjdGlvbg0KDQpUaGUgInNhbGFyeSIgY29sdW1uIGdpdmVzIHRoZSBzYW1lIGluZm9ybWF0aW9uIGFzICJzYWxhcnlfaW5fdXNkIiBpdCdzIGp1c3QgYSBtYXR0ZXIgb2YgY3VycmVuY3kgZXhjaGFuZ2UsIGFuZCB3ZSB3aWxsIGV2ZW50dWFsbHkgdHJhbnNmb3JtIGFsbCB0aGUgdmFsdWVzIGluICJzYWxhcnkiIGNvbHVtbiB0byBvbmUgY29tbW9uIGN1cnJlbmN5IHNvIHdlIGNhbiBwcm9wZXJseSBkZWFsIHdpdGggdGhlbS4gVG8gZnVydGhlciBjb25maXJtIHRoYXQgdGhlIHR3byBjb2x1bW4gYXJlIHJlZHVuZGFudCwgd2Ugd2lsbCB1c2UgdGhlIGxhdGVzdCBleGNoYW5nZSByYXRlIGZvciBVU0QgdG8gdGhlIGRlc2lyZWQgY3VycmVuY3kuDQoNCndlIHdpbGwgc3RhcnQgYnkgY3JlYXRpbmcgYSB0ZW1wb3JhcnkgY29sdW1uIG5hbWVkICJjb252ZXJ0ZWRfc2FsYXJ5IiB0byBzYXZlIHRoZSBzYWxhcnkgdGhhdCB3ZSB3aWxsIGdldCBieSB1c2luZyB0aGUgZXhjaGFuZ2UgcmF0ZSB0byBjb252ZXJ0IHRoZSBzYWxhcnlfaW5fdXNkIHRvIHRoZSBzYWxhcnkgd2l0aCBkaWZmZXJlbnQgY3VycmVuY2llcyB0byBjb21wYXJlIHdpdGggInNhbGFyeSIgY29sdW1uDQoNCmBgYHtyfQ0KY29udmVydGVkRGF0YXNldD1kYXRhc2V0DQoNCg0KY29udmVydGVkRGF0YXNldCRleGNoYW5nZV9yYXRlID0gZmFjdG9yKGNvbnZlcnRlZERhdGFzZXQkc2FsYXJ5X2N1cnJlbmN5LCBsZXZlbHM9YygiVVNEIiwiQlJMIiwiR0JQIiwiRVVSIiwiSU5SIiwiQ0FEIiwiQ0hGIiwiREtLIiwiU0dEIiwiQVVEIiwiU0VLIiwiTVhOIiwiSUxTIiwiUExOIiwiTk9LIiwiSURSIiwiTlpEIiwiSFVGIiwiWkFSIiwiVFdEIiwiUlVCIiksIGxhYmVscz1jKDEvMSwxLzAuMjAsMS8xLjIyLDEvMS4wNiwxLzAuMDEyLDEvMC43NCwxLzEuMTAsMS8wLjE0LDEvMC43MywxLzAuNjQsMS8wLjA5MCwxLzAuMDU3LDEvMC4yNiwxLzAuMjMsMS8wLjA5MywxLzAuMDAwMDY1LDEvMC42MCwxLzAuMDAyNywxLzAuMDUzLDEvMC4wMzEsMS8wLjAxMCkpDQpjb252ZXJ0ZWREYXRhc2V0JGV4Y2hhbmdlX3JhdGUgPSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcihjb252ZXJ0ZWREYXRhc2V0JGV4Y2hhbmdlX3JhdGUpKQ0KY29udmVydGVkRGF0YXNldCRjb252ZXJ0ZWRfc2FsYXJ5ID0gY29udmVydGVkRGF0YXNldCRzYWxhcnlfaW5fdXNkKmNvbnZlcnRlZERhdGFzZXQkZXhjaGFuZ2VfcmF0ZQ0KDQoNCg0Kc2V0LnNlZWQoMSkNCnNhbGFyeV9zYW1wbGUgPC0gc2FtcGxlX24oY29udmVydGVkRGF0YXNldFssYygic2FsYXJ5IiwiY29udmVydGVkX3NhbGFyeSIpXSwxMCkNCg0KcHJpbnQoc2FsYXJ5X3NhbXBsZSkNCmBgYA0KDQphcyBzaG93biBpbiB0aGUgc2FtcGxlLCB0aGUgdHdvIGNvbHVtbnMgYXJlIGFsbW9zdCBpZGVudGljYWwuIFRoaXMgY2FuIGJlIHByb3ZlZCBieSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCBhcyB3ZWxsLg0KDQpgYGB7cn0NCmNvcnJlbGF0aW9uIDwtIGNvcihjb252ZXJ0ZWREYXRhc2V0JHNhbGFyeSAsIGNvbnZlcnRlZERhdGFzZXQkY29udmVydGVkX3NhbGFyeSkNCnByaW50KGNvcnJlbGF0aW9uKQ0KYGBgDQoNClRoZSBjb3JyZWxhdGlvbiBpcyBzbyBoaWdoIGJ1dCBpdCBoYXNuJ3QgcmVhY2hlZCAxMDAlIHBvc3NpYmx5IGR1ZSB0byByb3VuZGluZyBpbiB0aGUgY2FsY3VsYXRpb25zIGFuZCBzbGlnaHQgZGlmZmVyZW5jZXMgaW4gdGhlIGV4Y2hhbmdlIHJhdGUgb3ZlciB0aW1lLg0KDQpUbyBtYWtlIHRoZSBtaW5pbmcgcHJvY2VzcyBtb3JlIGVmZmllY2VudCBhbmQgaGFzIGFuIGltcHJvdmVkIHF1YWxpdHksIHdlIGRlY2lkZWQgdG8gcmVtb3ZlIHRoZSAic2FsYXJ5IiBjb2x1bW4uDQoNCmBgYHtyfQ0KZGF0YXNldDwtZGF0YXNldFssLWMoNSldDQpgYGANCg0KIyMjIEZpbmQgdGhlIG91dGxpZXJzIGFuZCByZW1vdmUgdGhlbToNCg0KV2Ugd2lsbCBzaG93IG91dGxpZXJzIHdpdGggYm94UGxvdHMgYW5kIHRoZW4gcmVtb3ZlIHRoZW0sIHRvIG1pbmltaXplIG5vaXNlIGFuZCB0byBnZXQgYmV0dGVyIGFuYWx5dGljYWwgcmVzdWx0cyB3aGVuIGFwcGx5aW5nIGRhdGEgbWluaW5nIHRlY2huaXF1ZXMuDQoNCm5vdyB3ZSBzaG93IChzYWxhcnlfaW5fdXNkKSBhdHRyaWJ1dGVzJyBvdXRsaWVycy4gd2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBtYW55IG91dGxpZXJzIHdpdGggZXhjZXB0aW9uYWxseSBoaWdoIHZhbHVlcywgdGh1cyB3ZSB3aWxsIHJlbW92ZSB0aGVtLg0KDQpgYGB7cn0NCmJveHBsb3QoZGF0YXNldCRzYWxhcnlfaW5fdXNkKQ0KDQoNCg0KT3V0U2FsYXJ5ID0gb3V0bGllcihkYXRhc2V0JHNhbGFyeV9pbl91c2QsIGxvZ2ljYWwgPVRSVUUpDQpGaW5kX291dGxpZXIgPSB3aGljaChPdXRTYWxhcnkgPT1UUlVFLCBhcnIuaW5kID0gVFJVRSkNCmRhdGFzZXQ9IGRhdGFzZXRbLUZpbmRfb3V0bGllcixdDQoNCmBgYA0KDQpub3cgd2Ugc2hvdyAocmVtb3RlX3JhdGlvKSBhdHRyaWJ1dGVzJyBvdXRsaWVycy4gd2UgY2FuIHNlZSB0aGVyZSBhcmVuJ3Qgb3V0bGllcnMgaW4gcmVtb3RlX3JhdGlvLCB0aHVzIHdlIGRvbid0IG5lZWQgdGhlIGxhc3Qgc3RlcCBpLmU6IHJlbW92aW5nIG91dGxpZXJzJyByb3dzLg0KDQpgYGB7cn0NCmJveHBsb3QoZGF0YXNldCRyZW1vdGVfcmF0aW8pDQoNCmBgYA0KDQpub3cgd2Ugc2hvdyAod29ya195ZWFyKSBhdHRyaWJ1dGVzJyBvdXRsaWVycy4gd2UgY2FuIHNlZSB0aGVyZSBhcmVuJ3Qgb3V0bGllcnMgaW4gd29ya195ZWFyLCB0aHVzIHdlIGRvbid0IG5lZWQgdGhlIGxhc3Qgc3RlcCBpLmU6IHJlbW92aW5nIG91dGxpZXJzJyByb3dzLg0KDQpgYGB7cn0NCmJveHBsb3QoZGF0YXNldCR3b3JrX3llYXIpDQoNCmBgYA0KDQojIyMgQ29uY2VwdCBoaWVyYXJjaHkgZ2VuZXJhdGlvbiBmb3Igbm9taW5hbCBkYXRhDQoNCnRoZSBjb2x1bW5zICJjb21wYW55X2xvY2F0aW9uIiBhbmQgImVtcGxveWVlX3Jlc2lkZW5jZSIgaGF2ZSB0aGUgbmFtZSBvZiBjb3VudHJpZXMgZm9yIHRoZSBjb21wYW55IGFuZCBlbXBsb3llZSByZXNwZWN0aXZlbHkuIEFuZCB0aGVzZSBhdHRyaWJ1dGVzIGNhbiBiZSBnZW5lcmFsaXplZCB0byBoaWdoZXItbGV2ZWwgY29uY2VwdCB0aGF0IGlzIHJlZ2lvbiB0byBoZWxwIHVuZGVyc3RhbmQgYW5kIGFuYWx5emUgdGhlIGRhdGFzZXQgYmV0dGVyIGFuZCBpbXByb3ZlIGFsZ29yaXRobSBwZXJmb3JtYW5jZS4NCg0KV2Ugd2lsbCB1c2UgdGhlIDcgcmVnaW9ucyBhcyBkZWZpbmVkIGluIHRoZSBXb3JsZCBCYW5rIERldmVsb3BtZW50IEluZGljYXRvcnMuIFRoZXNlIHJlZ2lvbnMgYXJlOg0KDQoxLiAgRWFzdCBBc2lhIGFuZCBQYWNpZmljOiBUaGlzIHJlZ2lvbiBpbmNsdWRlcyBjb3VudHJpZXMgbGlrZSBDaGluYSwgQXVzdHJhbGlhLCBJbmRvbmVzaWEsIFRoYWlsYW5kLCBldGMuDQoNCjIuICBFdXJvcGUgYW5kIENlbnRyYWwgQXNpYTogVGhpcyByZWdpb24gaW5jbHVkZXMgY291bnRyaWVzIGxpa2UgR2VybWFueSwgVUssIFJ1c3NpYSwgVHVya2V5LCBldGMuDQoNCjMuICBMYXRpbiBBbWVyaWNhICYgQ2FyaWJiZWFuOiBUaGlzIHJlZ2lvbiBpbmNsdWRlcyBjb3VudHJpZXMgbGlrZSBCcmF6aWwsIE1leGljbywgQXJnZW50aW5hLCBDdWJhLCBldGMuDQoNCjQuICBNaWRkbGUgRWFzdCBhbmQgTm9ydGggQWZyaWNhOiBUaGlzIHJlZ2lvbiBpbmNsdWRlcyBjb3VudHJpZXMgbGlrZSBTYXVkaSBBcmFiaWEsIEVneXB0LCBJcmFuLCBJcmFxLCBldGMuDQoNCjUuICBOb3J0aCBBbWVyaWNhOiBUaGlzIGlzIHByZWRvbWluYW50bHkgVW5pdGVkIFN0YXRlcyBhbmQgQ2FuYWRhLg0KDQo2LiAgU291dGggQXNpYTogVGhpcyByZWdpb24gaW5jbHVkZXMgY291bnRyaWVzIGxpa2UgSW5kaWEsIFBha2lzdGFuLCBCYW5nbGFkZXNoLCBTcmkgTGFua2EsIGV0Yy4NCg0KNy4gIFN1Yi1TYWhhcmFuIEFmcmljYTogVGhpcyByZWdpb24gaW5jbHVkZXMgY291bnRyaWVzIGxpa2UgTmlnZXJpYSwgU291dGggQWZyaWNhLCBFdGhpb3BpYSwgS2VueWEsIGV0Yy4NCg0KTm90ZTogVU0oVGhlIFVuaXRlZCBTdGF0ZXMgTWlub3IgT3V0bHlpbmcgSXNsYW5kcykgYW5kIEFRKEFudGFyY3RpY2EpIGRvbid0IGJlbG9uZyB0byBhbnkgb2YgdGhlc2UgcmVnaW9ucywgdGh1cywgdGhleSB3aWxsIGJlIHVzZWQgYXMgdGhleSBhcmUuDQoNCmBgYHtyfQ0KDQoNCnVtPXdoaWNoKGRhdGFzZXQkY29tcGFueV9sb2NhdGlvbj09IlVNIikNCmFxPXdoaWNoKGRhdGFzZXQkY29tcGFueV9sb2NhdGlvbj09IkFRIikNCg0KDQpkYXRhc2V0JGNvbXBhbnlfbG9jYXRpb24gPC0gY291bnRyeWNvZGUoZGF0YXNldCRjb21wYW55X2xvY2F0aW9uLCAiaXNvMmMiLCAicmVnaW9uIikNCmRhdGFzZXQkZW1wbG95ZWVfcmVzaWRlbmNlIDwtIGNvdW50cnljb2RlKGRhdGFzZXQkZW1wbG95ZWVfcmVzaWRlbmNlLCAiaXNvMmMiLCAicmVnaW9uIikNCg0KZGF0YXNldFt1bSwiY29tcGFueV9sb2NhdGlvbiJdPSJVTSINCmRhdGFzZXRbYXEsImNvbXBhbnlfbG9jYXRpb24iXT0iQVEiDQoNCmBgYA0KDQpDb25jZXB0IGhpZXJhcmNoeSBnZW5lcmF0aW9uIGNhbiBiZSBkb25lIGZvciAiam9iX3RpdGxlIiBhcyB3ZWxsIHRvIGltcHJvdmUgaW50ZXJwcmV0YXRpb24gYW5kIHNjYWxhYmlsaXR5LiBBbHNvLCBtb3N0IGpvYiB0aXRsZXMgYXJlIGVzc2VudGlhbGx5IHRoZSBzYW1lIGpvYiBidXQgd2l0aCBkaWZmZXJlbnQgbmFtZXMsIHNvIHdlIGNhbiBjb21iaW5lIHRoZW0gaW50byBhIGhpZ2hlci1sZXZlbCBqb2JzIHRpdGxlcyBzdWNoIGFzIEFyY2hpdGVjdCwgQW5hbHlzdCBhbmQgRW5naW5lZXIgZXRjLg0KDQpgYGB7cn0NCiMjIENyZWF0ZSB0aGUgY2F0ZWdvcmllcyBiYXNlZCBvbiBqb2IgcmFuayANCmRhdGFzZXQkam9iX3RpdGxlIDwtIGlmZWxzZShncmVwbCgiQW5hbHlzdCIsIGRhdGFzZXQkam9iX3RpdGxlKSwgIkFuYWx5c3QiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIkFyY2hpdGVjdCIsIGRhdGFzZXQkam9iX3RpdGxlKSwgIkFyY2hpdGVjdCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIkVuZ2luZWVyIiwgZGF0YXNldCRqb2JfdGl0bGUpLCAiRW5naW5lZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgiTWFuYWdlcnxPZmZpY2VyfERpcmVjdG9yfExlYWRlciIsIGRhdGFzZXQkam9iX3RpdGxlKSwgIkxlYWRlcnNoaXAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIkNvbnN1bHRhbnR8U3BlY2lhbGlzdCIsIGRhdGFzZXQkam9iX3RpdGxlKSwgIkNvbnN1bHRhbnQvU3BlY2lhbGlzdCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoIkN5YmVyIiwgZGF0YXNldCRqb2JfdGl0bGUpLCAiQ3liZXIgU2VjdXJpdHkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPdGhlcnMiKSkpKSkpDQoNCmBgYA0KDQojIyBFbmNvZGluZyBjYXRlZ29yaWNhbCBkYXRhDQoNClRvIGRlYWwgd2l0aCBjb2x1bW5zIHdpdGggY2hhcmFjdGVyIHR5cGUgd2UgYXJlIGdvaW5nIHRvIGVuY29kZSB0aGVtLCBiZWNhdXNlIG1vc3QgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGFyZSBkZXNpZ25lZCB0byB3b3JrIHdpdGggZmFjdG9ycyBkYXRhIHJhdGhlciB0aGFuIGNoYXJhY3RlciBkYXRhIGFuZCBpdCBpbXByb3ZlcyBwZXJmb3JtYW5jZSBhbmQgSW50ZXJwcmV0YWJpbGl0eSBvZiBkYXRhIGFzIHdlbGwuDQoNCmBgYHtyfQ0KZGF0YXNldCRqb2JfdGl0bGUgIDwtIGZhY3RvcihkYXRhc2V0JGpvYl90aXRsZSkNCg0KZGF0YXNldCRleHBlcmllbmNlX2xldmVsID0gZmFjdG9yKGRhdGFzZXQkZXhwZXJpZW5jZV9sZXZlbCwgbGV2ZWxzPWMoIkVOIiwgIk1JIiwgIlNFIiwgIkVYIiksIGxhYmVscz1jKDEsMiwzLDQpKQ0KDQpkYXRhc2V0JGVtcGxveW1lbnRfdHlwZSAgPC0gZmFjdG9yKGRhdGFzZXQkZW1wbG95bWVudF90eXBlKQ0KDQpkYXRhc2V0JGVtcGxveWVlX3Jlc2lkZW5jZSAgPC0gZmFjdG9yKGRhdGFzZXQkZW1wbG95ZWVfcmVzaWRlbmNlKQ0KDQpkYXRhc2V0JGNvbXBhbnlfbG9jYXRpb24gIDwtIGZhY3RvcihkYXRhc2V0JGNvbXBhbnlfbG9jYXRpb24pDQoNCmRhdGFzZXQkc2FsYXJ5X2N1cnJlbmN5ICA8LSBmYWN0b3IoZGF0YXNldCRzYWxhcnlfY3VycmVuY3kpDQoNCmRhdGFzZXQkam9iX3RpdGxlICA8LSBmYWN0b3IoZGF0YXNldCRqb2JfdGl0bGUpDQoNCg0KZGF0YXNldCRjb21wYW55X3NpemUgPSBmYWN0b3IoZGF0YXNldCRjb21wYW55X3NpemUsIGxldmVscz1jKCJTIiwiTSIsIkwiKSwgbGFiZWxzPWMoMSwyLDMpKQ0KDQoNCmRhdGFzZXQkam9iX3RpdGxlICA8LSBmYWN0b3IoZGF0YXNldCRqb2JfdGl0bGUpDQoNCmBgYA0KDQojIyMgRGlzY3JldGl6YXRpb24gb2Ygc2FsYXJheV9pbl91c2QgYXR0cmlidXRlDQoNCmJ5IGNhbGN1bGF0aW5nIGJyZWFrcyBiYXNlZCBvbiBxdWFydGlsZXMNCg0KYGBge3J9DQpicmVha3MgPC0gcXVhbnRpbGUoZGF0YXNldCRzYWxhcnlfaW5fdXNkLCANCiAgICAgICAgICAgICAgICAgICBwcm9icyA9IGMoMCwgLjI1LCAuNSwgLjc1LCAuOTUsIDEpLCANCiAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUpDQoNCg0KZGF0YXNldCRzYWxhcnlfaW5fdXNkIDwtIGN1dChkYXRhc2V0JHNhbGFyeV9pbl91c2QsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYnJlYWtzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGUubG93ZXN0ID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiVmVyeSBMb3ciLCAiTG93IiwgIk1lZGl1bSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKQ0KDQoNCmBgYA0KDQojIyMgTm9ybWFsaXphdGlvbjoNCg0KdG8gY2hhbmdlIHRoZSBzY2FsZSBvZiBudW1lcmljIGF0dHJpYnV0ZXMgKHJlbW90ZV9yYXRpbyBhbmQgd29ya195ZWFyKSB0byBhIHNjYWxlIG9mIFstMSwxXSB0byBnaXZlIHRoZW0gZXF1YWwgd2VpZ2h0DQoNCmBgYHtyfQ0KZGF0YXNldCBbLCBjKCJ3b3JrX3llYXIiICwgInJlbW90ZV9yYXRpbyIpXSA9IHNjYWxlKGRhdGFzZXQgWywgYygid29ya195ZWFyIiAsICJyZW1vdGVfcmF0aW8iKV0pDQpgYGANCg0KIyMgRmVhdHVyZSBTZWxlY3Rpb24NCg0Kd2Ugd2lsbCBpbXBsZW1lbnQgZmVhdHVyZSBzZWxlY3Rpb24gdG8gcmVtb3ZlIHJlZHVuZGFudCBvciBpcnJlbGV2YW50IGF0dHJpYnV0ZXMgZnJvbSB0aGUgZGF0YSBzZXQgdG8gZ2V0IHRoZSBzbWFsbGVzdCBzdWJzZXQgdGhhdCBjYW4gaGVscCB1cyBnZXQgdGhlIG1vc3QgYWNjdXJhdGUgcHJlZGljdGlvbnMgZm9yIG91ciB0YXJnZXQgY2xhc3Moc2FsYXJ5X2luX3VzZCkgYW5kIGRlY3JlYXNlIHRoZSB0aW1lIHRoYXQgaXQgdGFrZXMgdGhlIGNsYXNzaWZpZXIgdG8gcHJvY2VzcyB0aGUgZGF0YS4NCg0Kd2Ugd2lsbCB1c2UgUkZFKFJlY3Vyc2l2ZSBmZWF0dXJlIGVsaW1pbmF0aW9uKSB3aGljaCBpcyBhIHdyYXBwZXIgbWV0aG9kIGZvciB0aGUgZmVhdHVyZSBzZWxlY3Rpb24uIFNpbmNlIHRoZSBSRkUgZnVuY3Rpb24gaGF2ZSBtdWx0aXBsZSBjb250cm9sIG9wdGlvbnMgd2UgbmVlZCB0byBzcGVjaWZ5IHRoZSBvcHRpb25zIHRoYXQgd2Ugd2FudC4gV2Ugd2lsbCBjaG9vc2UgIlJhbmRvbSBGb3Jlc3QiIGJlY2F1c2UgaXQgaGFzIGhpZ2ggYWNjdXJhY3ksIGNhbiBoYW5kbGUgY2F0ZWdvcmljYWwgZGF0YS4NCg0KYGBge3J9DQpjb250cm9sIDwtIHJmZUNvbnRyb2woZnVuY3Rpb25zID0gcmZGdW5jcywgDQogICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJlcGVhdGVkY3YiLA0KICAgICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSA1LCANCiAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAxMCkNCmBgYA0KDQpGaXJzdCB3ZSBzYXZlIHRoZSBmZWF0dXJlcyB0byBiZSB1c2VkIGluIHRoZSBmZWF0dXJlIHNlbGVjdGlvbihldmVyeSBhdHRyaWJ1dGVzIGV4Y2VwdCB0aGUgY2xhc3MgbGFiZWwgInNhbGFyeV9pbl91c2QiKSBpbiB2YXJpYWJsZSB4LCBhbmQgdGhlIGNsYXNzIGxhYmVsIGluIHZhcmlhYmxlIHkuIFRoZW4gc3BsaXQgdGhlIGRhdGEgdG8gODAlIHRyYWluaW5nIGFuZCAyMCUgdGVzdC4NCg0KYGBge3J9DQp4IDwtIGRhdGFzZXQgJT4lDQogIHNlbGVjdCgtc2FsYXJ5X2luX3VzZCkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKQ0KDQojIFRhcmdldCB2YXJpYWJsZQ0KeSA8LSBkYXRhc2V0JHNhbGFyeV9pbl91c2QNCg0KIyBUcmFpbmluZzogODAlOyBUZXN0OiAyMCUNCnNldC5zZWVkKDIwMjEpDQppblRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSwgcCA9IC44MCwgbGlzdCA9IEZBTFNFKVssMV0NCg0KeF90cmFpbiA8LSB4WyBpblRyYWluLCBdDQp4X3Rlc3QgIDwtIHhbLWluVHJhaW4sIF0NCg0KeV90cmFpbiA8LSB5WyBpblRyYWluXQ0KeV90ZXN0ICA8LSB5Wy1pblRyYWluXQ0KDQpgYGANCg0KYWZ0ZXIgc3BsaXR0aW5nIHRoZSBkYXRhLCBub3cgd2UgY2FuIHBlcmZvcm0gdGhlIHNlbGVjdGlvbiB1c2luZyByZmUNCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KcmVzdWx0X3JmZTEgPC0gcmZlKHggPSB4X3RyYWluLCANCiAgICAgICAgICAgICAgICAgICB5ID0geV90cmFpbiwgDQogICAgICAgICAgICAgICAgICAgc2l6ZXMgPSBjKDE6OSksDQogICAgICAgICAgICAgICAgICAgcmZlQ29udHJvbCA9IGNvbnRyb2wpDQoNCnJlc3VsdF9yZmUxDQoNCnByZWRpY3RvcnMocmVzdWx0X3JmZTEpDQoNCmBgYA0KDQpUaGUgcmVzdWx0cyBzaG93IHRoYXQgYWxsIHRoZSByZW1haW5pbmcgYXR0cmlidXRlcywgZXhjZXB0IGZvciAiZW1wbG95bWVudF90eXBlIiwgYXJlIHNlbGVjdGVkLiBUaGlzIGlzIGxvZ2ljYWwsIGFzIDk4JSBvZiB0aGUgcm93cyBoYXZlIHRoZSB2YWx1ZSAiRlQiLCBhcyBzaG93biBpbiB0aGUgdGFibGUgYmVsb3cuIER1ZSB0byB0aGUgbG93IHZhcmlhbmNlLCB3ZSBkZWNpZGVkIHRvIHJlbW92ZSB0aGlzIGF0dHJpYnV0ZS4NCg0KYGBge3J9DQp0YWJsZShkYXRhc2V0JGVtcGxveW1lbnRfdHlwZSkNCmBgYA0KDQpgYGB7cn0NCmRhdGFzZXQ8LWRhdGFzZXRbLC13aGljaChuYW1lcyhkYXRhc2V0KT09ImVtcGxveW1lbnRfdHlwZSIpXQ0KYGBgDQoNCg0KIyBwaGFzZSAzDQoNCg0KDQoNCg0KDQoNCg0KDQoNCmBgYHtyfQ0KZGF0YXNldDI9IHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1NhcmFoQWxoaW5kaS9ETV9wcm9qZWN0L21haW4vRGF0YSUyMFNldC9wcmVwcm9jZXNzZWREYXRhc2V0LmNzdiIpLCBoZWFkZXI9VFJVRSkNCg0KDQpjaGFyX3ZhcnMgPC0gc2FwcGx5KGRhdGFzZXQyLCBpcy5jaGFyYWN0ZXIpDQpkYXRhc2V0MltjaGFyX3ZhcnNdIDwtIGxhcHBseShkYXRhc2V0MltjaGFyX3ZhcnNdLCBhcy5mYWN0b3IpDQoNCmBgYA0KDQoNCg0KDQoNCg0KIyMgYmFsYW5jaW5nIGRhdGENCg0KVG8gcmVzb2x2ZSB0aGUgcHJvYmxlbSBvZiBjbGFzcyBpbWJhbGFuY2UgaW4gdGhlIGRhdGFzZXQsIHdlIHdpbGwgdXNlIFNNT1RFKCkgbWV0aG9kIHRoYXQgb3ZlcnNhbXBsZSB0aGUgbWlub3JpdHkgY2xhc3MgYnkgY3JlYXRpbmcgc3ludGhldGljIHNhbXBsZXMgdXNpbmcgdGhlIGV4aXN0aW5nIG1pbm9yaXR5IGNsYXNzIHNhbXBsZXMNCg0KYGBge3J9DQpkYXRhX2JhbGFuY2VkIDwtIFNNT1RFKHNhbGFyeV9pbl91c2QgfiAuLCBkYXRhc2V0MiwgcGVyYy5vdmVyID0gMzAwLCBwZXJjLnVuZGVyPTUwMCwgayA9IDEwKQ0KYGBgDQoNCiMjIENsYXNzaWZpY2F0aW9uDQoNClRoZSBnb2FsIG9mIGFsbCBwcmVjZWRpbmcgc3RlcHMgaXMgdG8gcHJvcGVybHkgcHJlcGFyZSB0aGUgZGF0YXNldCBmb3IgdGhlIGNsYXNzaWZpY2F0aW9uIHBoYXNlLCB3aGljaCBjb25zdGl0dXRlcyBvbmUgb2Ygb3VyIHByaW1hcnkgbWluaW5nIG9iamVjdGl2ZXMuIEluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBlbXBsb3kgdmFyaW91cyBhdHRyaWJ1dGUgc2VsZWN0aW9uIG1ldGhvZHMgc3VjaCBhcyB0aGUgR2luaSBpbmRleCwgR2FpbiByYXRpbywgYW5kIGluZm9ybWF0aW9uIGdhaW4gdG8gY29uc3RydWN0IGEgZGVjaXNpb24gdHJlZSBtb2RlbC4gV2Ugd2lsbCB0aG9yb3VnaGx5IGV2YWx1YXRlIGl0cyBwZXJmb3JtYW5jZSwgYW5kIGlmIGl0IHByb3ZlcyBlZmZlY3RpdmUsIGl0IGNhbiBzdWJzZXF1ZW50bHkgYmUgdXRpbGl6ZWQgdG8gY2xhc3NpZnkgbmV3IGluc3RhbmNlcyB3aXRoIHVua25vd24gY2xhc3MgbGFiZWxzLg0KDQpzaW5jZSBvdXIgZGF0YXNldCBpcyBzbWFsbCwgd2UgZGVjaWRlZCB0byB1c2UgSy1mb2xkIENyb3NzLXZhbGlkYXRpb24uIGZvciBlYWNoIGF0dHJpYnV0ZSBzZWxlY3Rpb24gbWV0aG9kIHdlIHdpbGwgdHJ5IGRpZmZlcmVudCBLIHNpemUgKDEwLDUsIGFuZCAzKQ0KDQoNCg0KdGhlIGZvbGxvd2luZyBmdW5jdGlvbiB3aWxsIGJlIHVzZWQgdG8gY29wdXRlIGF2ZXJhZ2Ugc2Vuc2l0aXZpdHkgYW5kIFNwZWNpZmljaXR5DQpgYGB7cn0NCg0KDQptYWNybyA9IGZ1bmN0aW9uKG1hdHJpeCl7DQogIA0KICBzdW1TZW49MA0KICANCiAgZm9yIChpIGluIDE6NSkgew0KICAgc3VtU2VuID0gc3VtU2VuICsgbWF0cml4JGJ5Q2xhc3NbaSwxXSANCiAgfQ0KICANCiAgDQogIGF2Z1NlbiA9IHN1bVNlbi81DQogIA0KICBzdW1TcGVjPTANCiAgDQogIGZvciAoaSBpbiAxOjUpIHsNCiAgIHN1bVNwZWMgPSBzdW1TcGVjICsgbWF0cml4JGJ5Q2xhc3NbaSwyXSANCiAgfQ0KICBhdmdTcGVjID0gc3VtU3BlYy81DQogIA0KICANCiAgYXZncyA9IGRhdGEuZnJhbWUoU2Vuc2l0aXZpdHk9YXZnU2VuICwgU3BlY2lmaWNpdHk9YXZnU3BlYywgQWNjdXJhY3k9IHVubmFtZSggbWF0cml4JG92ZXJhbGxbMV0pICkNCiAgcHJpbnQoYXZncykNCiAgDQogIA0KfQ0KDQpgYGANCg0KDQojIyMgR2luaSBpbmRleA0KDQpHaW5pIGluZGV4IG1lYXN1cmVzIHRoZSBpbXB1cml0eSBvZiB0aGUgZGF0YXNldC4gVGhlIHBhcnRpdGlvbmluZyB0aGF0IHlpZWxkcyB0aGUgbW9zdCBzdWJzdGFudGlhbCByZWR1Y3Rpb24gaW4gaW1wdXJpdHkgaXMgc2VsZWN0ZWQgYXMgdGhlIHNwbGl0dGluZyBhdHRyaWJ1dGUuIFRvIGFwcGx5IHRoZSBHaW5pIGluZGV4LCB3ZSB3aWxsIGVtcGxveSB0aGUgInJwYXJ0IiBtZXRob2QsIHdoaWNoIHV0aWxpemVzIHRoZSBHaW5pIGluZGV4IGFzIHRoZSBjcml0ZXJpYSBmb3Igc3BsaXR0aW5nLg0KDQojIyMjIyAxMCBGb2xkcw0KDQpgYGB7cn0NCnNldC5zZWVkKDEwKQ0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTAsIHJldHVyblJlc2FtcD0iYWxsIiwgc2F2ZVByZWRpY3Rpb25zPSJmaW5hbCIpDQoNCmdpbmlJbmRleDEwIDwtIHRyYWluKHNhbGFyeV9pbl91c2QgfiAuLCBkYXRhID0gYmFsYW5jZWRfZGF0YXNldCwgbWV0aG9kID0gInJwYXJ0Iix0ckNvbnRyb2wgPSBjdHJsKQ0KDQpwcnAoZ2luaUluZGV4MTAkZmluYWxNb2RlbCwgYm94LnBhbGV0dGUgPSAiUmVkcyIsIHR3ZWFrID0gMS4yLCB2YXJsZW4gPSAyMCkNCg0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KGdpbmlJbmRleDEwJHByZWQkb2JzLGdpbmlJbmRleDEwJHByZWQkcHJlZCkNCg0KYGBgDQoNCiMjIyMjIDUgRm9sZHMNCg0KYGBge3J9DQpzZXQuc2VlZCgxMCkNCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDUsIHJldHVyblJlc2FtcD0iYWxsIiwgc2F2ZVByZWRpY3Rpb25zPSJmaW5hbCIpDQoNCmdpbmlJbmRleDUgPC0gdHJhaW4oc2FsYXJ5X2luX3VzZCB+IC4sIGRhdGEgPSBiYWxhbmNlZF9kYXRhc2V0LCBtZXRob2QgPSAicnBhcnQiLHRyQ29udHJvbCA9IGN0cmwpDQoNCnBycChnaW5pSW5kZXg1JGZpbmFsTW9kZWwsIGJveC5wYWxldHRlID0gIlJlZHMiLCB0d2VhayA9IDEuMiwgdmFybGVuID0gMjApDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KGdpbmlJbmRleDUkcHJlZCRvYnMsZ2luaUluZGV4NSRwcmVkJHByZWQpDQoNCmBgYA0KDQojIyMjIyAzIEZvbGRzDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTApDQpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAzLCByZXR1cm5SZXNhbXA9ImFsbCIsIHNhdmVQcmVkaWN0aW9ucz0iZmluYWwiKQ0KDQpnaW5pSW5kZXgzIDwtIHRyYWluKHNhbGFyeV9pbl91c2QgfiAuLCBkYXRhID0gYmFsYW5jZWRfZGF0YXNldCwgbWV0aG9kID0gInJwYXJ0Iix0ckNvbnRyb2wgPSBjdHJsKQ0KDQpwcnAoZ2luaUluZGV4MyRmaW5hbE1vZGVsLCBib3gucGFsZXR0ZSA9ICJSZWRzIiwgdHdlYWsgPSAxLjIsIHZhcmxlbiA9IDIwKQ0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChnaW5pSW5kZXgzJHByZWQkb2JzLGdpbmlJbmRleDMkcHJlZCRwcmVkKQ0KDQpgYGANCg0KDQoNCg0KIyMjIEdhaW4gcmF0aW8NCg0KDQpUaGUgZ2FpbiByYXRpbywgYSBub3JtYWxpemVkIG1lYXN1cmUgb2YgaW5mb3JtYXRpb24gZ2FpbiwgaXMgY2FsY3VsYXRlZCBieSBkaXZpZGluZyBpbmZvcm1hdGlvbiBnYWluIGJ5IHRoZSBzcGxpdCBpbmZvcm1hdGlvbi4gVGhlIGF0dHJpYnV0ZSB0aGF0IHlpZWxkcyB0aGUgaGlnaGVzdCBnYWluIHJhdGlvIGlzIGNob3NlbiBhcyB0aGUgc3BsaXR0aW5nIGF0dHJpYnV0ZS4gVGhlIEM0LjUgYWxnb3JpdGhtIGVtcGxveXMgdGhlIGdhaW4gcmF0aW8uDQoNClRoZSBKNDggaXMgdGhlIEphdmEtYmFzZWQgb3Blbi1zb3VyY2UgaW1wbGVtZW50YXRpb24gb2YgdGhlIEM0LjUgYWxnb3JpdGhtLCBhbmQgaXQgaXMgaW5jbHVkZWQgaW4gdGhlIFdla2EgcGFja2FnZS4gVGhpcyBpbXBsZW1lbnRhdGlvbiBhbGxvd3MgdXNlcnMgdG8gY29udmVuaWVudGx5IGFwcGx5IHRoZSBDNC41IGRlY2lzaW9uIHRyZWUuDQoNCg0KDQojIyMjIDEwIEZvbGRzDQoNCmBgYHtyICwgZmlnLmhlaWdodD03MCwgZmlnLndpZHRoPTkwfQ0Kc2V0LnNlZWQoMTApDQpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCwgcmV0dXJuUmVzYW1wPSJhbGwiLCBzYXZlUHJlZGljdGlvbnM9ImZpbmFsIikNCmdhaW5SYXRpbzEwIDwtIHRyYWluKHNhbGFyeV9pbl91c2QgfiAuLCBkYXRhID0gYmFsYW5jZWRfZGF0YXNldCwgbWV0aG9kID0gIko0OCIsdHJDb250cm9sID0gY3RybCkNCnBsb3QoZ2FpblJhdGlvMTAkZmluYWxNb2RlbCkNCmBgYA0KDQoNCg0KDQoNCmBgYHtyfQ0KZ2FpblJhdGlvMTBjbSA9IGNhcmV0Ojpjb25mdXNpb25NYXRyaXgoZ2FpblJhdGlvMTAkcHJlZCRvYnMsIGdhaW5SYXRpbzEwJHByZWQkcHJlZCkNCg0KZ2FpblJhdGlvMTBjbQ0KDQoNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCiMjIyMgNSBGb2xkcw0KDQpgYGB7ciAsIGZpZy5oZWlnaHQ9NzAsIGZpZy53aWR0aD05MH0NCnNldC5zZWVkKDEwKQ0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gNSwgcmV0dXJuUmVzYW1wPSJhbGwiLCBzYXZlUHJlZGljdGlvbnM9ImZpbmFsIikNCmdhaW5SYXRpbzUgPC0gdHJhaW4oc2FsYXJ5X2luX3VzZCB+IC4sIGRhdGEgPSBiYWxhbmNlZF9kYXRhc2V0LCBtZXRob2QgPSAiSjQ4Iix0ckNvbnRyb2wgPSBjdHJsKQ0KcGxvdChnYWluUmF0aW81JGZpbmFsTW9kZWwpDQpgYGANCg0KDQoNCg0KDQpgYGB7cn0NCg0KZ2FpblJhdGlvNWNtPWNhcmV0Ojpjb25mdXNpb25NYXRyaXgoZ2FpblJhdGlvNSRwcmVkJG9icywgZ2FpblJhdGlvNSRwcmVkJHByZWQpDQoNCmdhaW5SYXRpbzVjbQ0KDQpgYGANCg0KDQoNCiMjIyMgMyBGb2xkcw0KDQpgYGB7ciwgZmlnLmhlaWdodD03MCwgZmlnLndpZHRoPTkwfQ0Kc2V0LnNlZWQoMTApDQpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAzLCByZXR1cm5SZXNhbXA9ImFsbCIsIHNhdmVQcmVkaWN0aW9ucz0iZmluYWwiKQ0KZ2FpblJhdGlvMyA8LSB0cmFpbihzYWxhcnlfaW5fdXNkIH4gLiwgZGF0YSA9IGJhbGFuY2VkX2RhdGFzZXQsIG1ldGhvZCA9ICJKNDgiLHRyQ29udHJvbCA9IGN0cmwpDQpwbG90KGdhaW5SYXRpbzMkZmluYWxNb2RlbCkNCmBgYA0KDQoNCmBgYHtyfQ0KZ2FpblJhdGlvM2NtPWNhcmV0Ojpjb25mdXNpb25NYXRyaXgoZ2FpblJhdGlvMyRwcmVkJG9icywgZ2FpblJhdGlvMyRwcmVkJHByZWQpDQoNCmdhaW5SYXRpbzNjbQ0KDQpgYGANCg0KDQoNCg0KDQojIyMgYW5hbGFzeXMgb2YgdGhlIGdhaW4gcmF0aW8gY2xhc3NpZmljYXRpb24NCg0KYWxsIDMgdHJlZXMgc2VlbSB0byBoYXZlIHRoZSBzYW1lIHN0cnVjdHVyZSB0aGF0IGlzDQoNCnRoZSBhdHRyaWJ1dGUgdGhhdCB3YXMgZmlyc3Qgc2VsZWN0ZWQgYXQgdGhlIG5vZGUgaXMgdGhlIGV4cGVyaWVuY2UgbGV2ZWwsIGl0IGhhcyBkaXZpZGVkIHRoZSB0cmVlIGludG8gOg0KcmlnaHQgc3VidHJlZSA6IFNFKFNlbmlvciBsZXZlbCkgRVgoRXhlY3V0aXZlIGxldmVsKQ0KbGVmdCBzdWJ0cmVlIDogRU4oRW50cnktbGV2ZWwpICAgTUkoTWlkIGxldmVsKQ0KDQpFYWNoIG9mIHRoZXNlIHN1YnRyZWVzIGZ1cnRoZXIgcmVmaW5lcyB0aGUgY2xhc3NpZmljYXRpb24gYmFzZWQgb24gdGhlIGF0dHJpYnV0ZSAiZW1wbG95ZWUgcmVzaWRlbmNlLiIgSG93ZXZlciwgdGhlcmUgYXJlIGRpZmZlcmVudCBjcml0ZXJpYSBmb3Igc3BsaXR0aW5nIGluIHRoZSByaWdodCBhbmQgbGVmdCBzdWJ0cmVlczoNCg0KSW4gdGhlIFJpZ2h0IFN1YnRyZWU6DQoNClRoZSBzcGxpdCBpcyBiYXNlZCBvbiB3aGV0aGVyIHRoZSB0dXBsZSBoYXMgdGhlIHZhbHVlICJMYXRpbiBBbWVyaWNhICYgQ2FyaWJiZWFuLiINCkluIHRoZSBMZWZ0IFN1YnRyZWU6DQoNCklmIHRoZSBleHBlcmllbmNlIGxldmVsIGlzIDEsIHRoZSB0cmVlIGZ1cnRoZXIgcGFydGl0aW9ucyBiYXNlZCBvbiB3aGV0aGVyIHRoZSB0dXBsZSBoYXMgdGhlIHZhbHVlICJOb3J0aCBBbWVyaWNhLiINCklmIHRoZSBleHBlcmllbmNlIGxldmVsIGlzIDIsIHRoZSBzcGxpdCBpcyBiYXNlZCBvbiAiZW1wbG95ZWUgcmVzaWRlbmNlIiBiZWluZyAiTGF0aW4gQW1lcmljYSAmIENhcmliYmVhbi4iDQoNCg0KDQpgYGB7cn0NCnJiaW5kKCIxMCBGb2xkcyI9bWFjcm8oZ2FpblJhdGlvMTBjbSksICI1IEZvbGRzIj1tYWNybyhnYWluUmF0aW81Y20pLCAiMyBGb2xkcyI9bWFjcm8oZ2FpblJhdGlvM2NtKSAgKQ0KYGBgDQoNCg0KDQpCYXNlZCBvbiB0aGUgZXZhbHVhdGlvbiBtZXRyaWNzIG9mIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSwgYW5kIEFjY3VyYWN5LCBpdCBpcyBldmlkZW50IHRoYXQgdGhlIGdhaW4gcmF0aW8gbW9kZWwsIGJ1aWx0IHVzaW5nIGEgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGFwcHJvYWNoLCBleGhpYml0cyBzdXBlcmlvciBwZXJmb3JtYW5jZSBjb21wYXJlZCB0byB0aGUgb3RoZXIgdHdvIG1vZGVscy4gSG93ZXZlciwgaXQncyB3b3J0aCBub3RpbmcgdGhhdCB0aGUgZGlmZmVyZW5jZSBpbiBwZXJmb3JtYW5jZSBiZXR3ZWVuIHRoZSBtb2RlbHMgaXMgcmVsYXRpdmVseSBzbWFsbC4gTm90YWJseSwgYXMgdGhlIG51bWJlciBvZiBmb2xkcyBkZWNyZWFzZXMsIGEgY29ycmVzcG9uZGluZyBkZWNsaW5lIGluIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGJlY29tZXMgYXBwYXJlbnQuDQoNCg0KDQoNCg0KDQoNCg0KDQojIyMgSW5mb3JtYXRpb24gZ2Fpbg0KDQojIyMjIDEwIEZvbGRzDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTApDQpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCwgcmV0dXJuUmVzYW1wPSJhbGwiLCBzYXZlUHJlZGljdGlvbnM9ImZpbmFsIikNCg0KDQppbmZvR2FpbjEwIDwtIHRyYWluKHNhbGFyeV9pbl91c2QgfiAuLCBkYXRhID0gYmFsYW5jZWRfZGF0YXNldCwgbWV0aG9kID0gIkM1LjAiLHRyQ29udHJvbCA9IGN0cmwpDQoNCmM1bW9kZWwgPC0gQzUuMChzYWxhcnlfaW5fdXNkIH4gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfYmFsYW5jZWQsDQogICAgICAgICAgICAgICAgICAgICAgIHRyaWFscyA9IGluZm9HYWluMTAkYmVzdFR1bmUkdHJpYWxzLCANCiAgICAgICAgICAgICAgICAgICAgICAgcnVsZXMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IEM1LjBDb250cm9sKHdpbm5vdyA9IGluZm9HYWluMTAkYmVzdFR1bmUkd2lubm93KSkNCnBsb3QoYzVtb2RlbCkNCmBgYA0KDQpgYGB7cn0NCmNhcmV0Ojpjb25mdXNpb25NYXRyaXgoaW5mb0dhaW4xMCRwcmVkJG9icywgaW5mb0dhaW4xMCRwcmVkJHByZWQpDQoNCmBgYA0KDQojIyMjIDUgRm9sZHMNCg0KYGBge3J9DQpzZXQuc2VlZCgxMCkNCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDUsIHJldHVyblJlc2FtcD0iYWxsIiwgc2F2ZVByZWRpY3Rpb25zPSJmaW5hbCIpDQoNCg0KaW5mb0dhaW41IDwtIHRyYWluKHNhbGFyeV9pbl91c2QgfiAuLCBkYXRhID0gYmFsYW5jZWRfZGF0YXNldCwgbWV0aG9kID0gIkM1LjAiLHRyQ29udHJvbCA9IGN0cmwpDQoNCmM1bW9kZWwgPC0gQzUuMChzYWxhcnlfaW5fdXNkIH4gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfYmFsYW5jZWQsDQogICAgICAgICAgICAgICAgICAgICAgIHRyaWFscyA9IGluZm9HYWluNSRiZXN0VHVuZSR0cmlhbHMsIA0KICAgICAgICAgICAgICAgICAgICAgICBydWxlcyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gQzUuMENvbnRyb2wod2lubm93ID0gaW5mb0dhaW41JGJlc3RUdW5lJHdpbm5vdykpDQpwbG90KGM1bW9kZWwpDQpgYGANCg0KYGBge3J9DQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KGluZm9HYWluNSRwcmVkJG9icywgaW5mb0dhaW41JHByZWQkcHJlZCkNCg0KYGBgDQoNCiMjIyMgMyBGb2xkcw0KDQpgYGB7cn0NCnNldC5zZWVkKDEwKQ0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMywgcmV0dXJuUmVzYW1wPSJhbGwiLCBzYXZlUHJlZGljdGlvbnM9ImZpbmFsIikNCg0KDQppbmZvR2FpbjMgPC0gdHJhaW4oc2FsYXJ5X2luX3VzZCB+IC4sIGRhdGEgPSBiYWxhbmNlZF9kYXRhc2V0LCBtZXRob2QgPSAiQzUuMCIsdHJDb250cm9sID0gY3RybCkNCg0KYzVtb2RlbCA8LSBDNS4wKHNhbGFyeV9pbl91c2QgfiAuLA0KICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YV9iYWxhbmNlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgdHJpYWxzID0gaW5mb0dhaW4zJGJlc3RUdW5lJHRyaWFscywgDQogICAgICAgICAgICAgICAgICAgICAgIHJ1bGVzID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBDNS4wQ29udHJvbCh3aW5ub3cgPSBpbmZvR2FpbjMkYmVzdFR1bmUkd2lubm93KSkNCnBsb3QoYzVtb2RlbCkNCmBgYA0KDQpgYGB7cn0NCmNhcmV0Ojpjb25mdXNpb25NYXRyaXgoaW5mb0dhaW4zJHByZWQkb2JzLCBpbmZvR2FpbjMkcHJlZCRwcmVkKQ0KDQpgYGANCg0KDQoNCg0KIyMjIFNpbGhvdWV0dGUgbWV0aG9kDQoNCmBgYHtyfQ0KI2EpZnZpel9uYmNsdXN0KCkgd2l0aCBzaWxob3VldHRlIG1ldGhvZCB1c2luZyBsaWJyYXJ5KGZhY3RvZXh0cmEpIA0KZnZpel9uYmNsdXN0KGRhdGFzZXQsIGttZWFucywgbWV0aG9kID0gInNpbGhvdWV0dGUiKSsNCiAgbGFicyhzdWJ0aXRsZSA9ICJTaWxob3VldHRlIG1ldGhvZCIpDQojYikgTmJDbHVzdCB2YWxpZGF0aW9uDQpmcmVzLm5iY2x1c3QgPC0gTmJDbHVzdChkYXRhc2V0LCBkaXN0YW5jZT0iZXVjbGlkZWFuIiwgbWluLm5jID0gMiwgbWF4Lm5jID0gMTAsIG1ldGhvZD0ia21lYW5zIiwgaW5kZXg9ImFsbCIpDQoNCmBgYA0KDQo=